mirror of
https://github.com/HuskyHacks/windows-x64-shellcode-pipeline
synced 2026-06-06 15:54:25 +00:00
adding source files
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
build/*
|
||||
tools/__pycache__
|
||||
@@ -0,0 +1,32 @@
|
||||
#include "runtime.h"
|
||||
|
||||
void payload_main(SC_ENV *env) {
|
||||
if (!env->pLoadLibraryA || !env->pGetProcAddress) {
|
||||
return;
|
||||
}
|
||||
|
||||
char user32_dll_name[] = { 'u','s','e','r','3','2','.','d','l','l', 0 };
|
||||
char message_box_name[] = { 'M','e','s','s','a','g','e','B','o','x','W', 0 };
|
||||
|
||||
wchar_t msg_content[] = { 'H','e','l','l','o',' ','W','o','r','l','d','!', 0 };
|
||||
wchar_t msg_title[] = { 'D','e','m','o','!', 0 };
|
||||
|
||||
HMODULE u32 = env->pLoadLibraryA(user32_dll_name);
|
||||
if (!u32) {
|
||||
return;
|
||||
}
|
||||
|
||||
int (WINAPI *pMessageBoxW)(
|
||||
HWND,
|
||||
LPCWSTR,
|
||||
LPCWSTR,
|
||||
UINT
|
||||
) = (int (WINAPI*)(HWND, LPCWSTR, LPCWSTR, UINT))
|
||||
env->pGetProcAddress(u32, message_box_name);
|
||||
|
||||
if (!pMessageBoxW) {
|
||||
return;
|
||||
}
|
||||
|
||||
pMessageBoxW(0, msg_content, msg_title, MB_OK);
|
||||
}
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
// Adapted from "From a C project, through assembly, to shellcode"
|
||||
// by hasherezade for @vxunderground
|
||||
// Ref: https://raw.githubusercontent.com/hasherezade/masm_shc/master/docs/FromaCprojectthroughassemblytoshellcode.pdf
|
||||
|
||||
#include "runtime.h"
|
||||
|
||||
#ifndef TO_LOWERCASE
|
||||
#define TO_LOWERCASE(out, c1) (out = (c1 <= 'Z' && c1 >= 'A') ? c1 = (c1 - 'A') + 'a' : c1)
|
||||
#endif
|
||||
|
||||
typedef struct _UNICODE_STRING {
|
||||
USHORT Length;
|
||||
USHORT MaximumLength;
|
||||
PWSTR Buffer;
|
||||
} UNICODE_STRING, *PUNICODE_STRING;
|
||||
|
||||
typedef struct _PEB_LDR_DATA {
|
||||
ULONG Length;
|
||||
BOOLEAN Initialized;
|
||||
HANDLE SsHandle;
|
||||
LIST_ENTRY InLoadOrderModuleList;
|
||||
LIST_ENTRY InMemoryOrderModuleList;
|
||||
LIST_ENTRY InInitializationOrderModuleList;
|
||||
PVOID EntryInProgress;
|
||||
} PEB_LDR_DATA, *PPEB_LDR_DATA;
|
||||
|
||||
typedef struct _LDR_DATA_TABLE_ENTRY {
|
||||
LIST_ENTRY InLoadOrderModuleList;
|
||||
LIST_ENTRY InMemoryOrderModuleList;
|
||||
LIST_ENTRY InInitializationOrderModuleList;
|
||||
void* BaseAddress;
|
||||
void* EntryPoint;
|
||||
ULONG SizeOfImage;
|
||||
UNICODE_STRING FullDllName;
|
||||
UNICODE_STRING BaseDllName;
|
||||
ULONG Flags;
|
||||
SHORT LoadCount;
|
||||
SHORT TlsIndex;
|
||||
HANDLE SectionHandle;
|
||||
ULONG CheckSum;
|
||||
ULONG TimeDateStamp;
|
||||
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
|
||||
|
||||
typedef struct _PEB {
|
||||
BOOLEAN InheritedAddressSpace;
|
||||
BOOLEAN ReadImageFileExecOptions;
|
||||
BOOLEAN BeingDebugged;
|
||||
BOOLEAN SpareBool;
|
||||
HANDLE Mutant;
|
||||
PVOID ImageBaseAddress;
|
||||
PPEB_LDR_DATA Ldr;
|
||||
} PEB, *PPEB;
|
||||
|
||||
static LPVOID get_module_by_name(WCHAR* module_name) {
|
||||
PPEB peb = NULL;
|
||||
#if defined(_WIN64)
|
||||
peb = (PPEB)__readgsqword(0x60);
|
||||
#else
|
||||
peb = (PPEB)__readfsdword(0x30);
|
||||
#endif
|
||||
PPEB_LDR_DATA ldr = peb->Ldr;
|
||||
LIST_ENTRY *head = &ldr->InLoadOrderModuleList;
|
||||
LIST_ENTRY *curr = head->Flink;
|
||||
|
||||
while (curr && curr != head) {
|
||||
PLDR_DATA_TABLE_ENTRY mod = (PLDR_DATA_TABLE_ENTRY)curr;
|
||||
if (mod->BaseDllName.Buffer != NULL) {
|
||||
WCHAR *curr_name = mod->BaseDllName.Buffer;
|
||||
size_t i = 0;
|
||||
for (i = 0; module_name[i] != 0 && curr_name[i] != 0; i++) {
|
||||
WCHAR c1, c2;
|
||||
TO_LOWERCASE(c1, module_name[i]);
|
||||
TO_LOWERCASE(c2, curr_name[i]);
|
||||
if (c1 != c2) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (module_name[i] == 0 && curr_name[i] == 0) {
|
||||
return mod->BaseAddress;
|
||||
}
|
||||
}
|
||||
curr = curr->Flink;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static LPVOID get_func_by_name(LPVOID module, char* func_name) {
|
||||
IMAGE_DOS_HEADER* idh = (IMAGE_DOS_HEADER*)module;
|
||||
if (idh->e_magic != IMAGE_DOS_SIGNATURE) {
|
||||
return NULL;
|
||||
}
|
||||
IMAGE_NT_HEADERS* nt_headers = (IMAGE_NT_HEADERS*)((BYTE*)module + idh->e_lfanew);
|
||||
IMAGE_DATA_DIRECTORY* exportsDir = &(nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]);
|
||||
if (exportsDir->VirtualAddress == 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
DWORD expAddr = exportsDir->VirtualAddress;
|
||||
IMAGE_EXPORT_DIRECTORY* exp = (IMAGE_EXPORT_DIRECTORY*)(expAddr + (ULONG_PTR)module);
|
||||
SIZE_T namesCount = exp->NumberOfNames;
|
||||
|
||||
DWORD funcsListRVA = exp->AddressOfFunctions;
|
||||
DWORD funcNamesListRVA = exp->AddressOfNames;
|
||||
DWORD namesOrdsListRVA = exp->AddressOfNameOrdinals;
|
||||
|
||||
for (SIZE_T i = 0; i < namesCount; i++) {
|
||||
DWORD* nameRVA = (DWORD*)((BYTE*)module + funcNamesListRVA + i * sizeof(DWORD));
|
||||
WORD* nameIndex = (WORD*)((BYTE*)module + namesOrdsListRVA + i * sizeof(WORD));
|
||||
DWORD* funcRVA = (DWORD*)((BYTE*)module + funcsListRVA + (*nameIndex) * sizeof(DWORD));
|
||||
|
||||
LPSTR curr_name = (LPSTR)((BYTE*)module + *nameRVA);
|
||||
size_t k = 0;
|
||||
for (k = 0; func_name[k] != 0 && curr_name[k] != 0; k++) {
|
||||
if (func_name[k] != curr_name[k]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (func_name[k] == 0 && curr_name[k] == 0) {
|
||||
return (BYTE*)module + (*funcRVA);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void sc_init_env(SC_ENV *env) {
|
||||
WCHAR kernel32_dll_name[] = { 'k','e','r','n','e','l','3','2','.','d','l','l', 0 };
|
||||
char load_lib_name[] = { 'L','o','a','d','L','i','b','r','a','r','y','A',0 };
|
||||
char get_proc_name[] = { 'G','e','t','P','r','o','c','A','d','d','r','e','s','s', 0 };
|
||||
|
||||
LPVOID base = get_module_by_name(kernel32_dll_name);
|
||||
if (!base) {
|
||||
env->kernel32 = NULL;
|
||||
env->pLoadLibraryA = NULL;
|
||||
env->pGetProcAddress = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
env->kernel32 = (HMODULE)base;
|
||||
env->pLoadLibraryA = (HMODULE (WINAPI*)(LPCSTR))get_func_by_name(base, load_lib_name);
|
||||
env->pGetProcAddress = (FARPROC (WINAPI*)(HMODULE, LPCSTR))get_func_by_name(base, get_proc_name);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
#include <windows.h>
|
||||
|
||||
typedef struct _SC_ENV {
|
||||
HMODULE kernel32;
|
||||
HMODULE (WINAPI *pLoadLibraryA)(LPCSTR);
|
||||
FARPROC (WINAPI *pGetProcAddress)(HMODULE, LPCSTR);
|
||||
} SC_ENV;
|
||||
|
||||
void sc_init_env(SC_ENV *env);
|
||||
@@ -0,0 +1,13 @@
|
||||
#include "runtime.h"
|
||||
|
||||
void payload_main(SC_ENV *env);
|
||||
|
||||
int main(void) {
|
||||
SC_ENV env;
|
||||
sc_init_env(&env);
|
||||
payload_main(&env);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#include "runtime.c"
|
||||
#include "payload_msgbox.c"
|
||||
@@ -0,0 +1,82 @@
|
||||
# Ref: https://github.com/mattifestation/PIC_Bindshell/blob/master/PIC_Bindshell/AdjustStack.asm#L24
|
||||
ALIGN_STUB = r"""
|
||||
.p2align 4
|
||||
.globl AlignRSP
|
||||
AlignRSP:
|
||||
push rsi
|
||||
mov rsi, rsp
|
||||
and rsp, -16
|
||||
sub rsp, 0x20
|
||||
call main
|
||||
mov rsp, rsi
|
||||
pop rsi
|
||||
ret
|
||||
"""
|
||||
|
||||
_META_PREFIXES = (
|
||||
".file",
|
||||
".ident",
|
||||
".cfi_",
|
||||
".loc",
|
||||
".def",
|
||||
".scl",
|
||||
".type",
|
||||
".endef",
|
||||
".seh_",
|
||||
".linkonce",
|
||||
)
|
||||
|
||||
_ALIGN_PREFIXES = (
|
||||
".p2align",
|
||||
".align",
|
||||
".balign",
|
||||
)
|
||||
|
||||
|
||||
def _should_drop_line(stripped: str) -> bool:
|
||||
if stripped.startswith(_META_PREFIXES):
|
||||
return True
|
||||
if stripped.startswith(".extern") or stripped.startswith("EXTERN"):
|
||||
return True
|
||||
if "__main" in stripped:
|
||||
return True
|
||||
if stripped.startswith(_ALIGN_PREFIXES):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def clean_asm_source(text: str) -> str:
|
||||
lines = text.splitlines()
|
||||
cleaned = []
|
||||
align_inserted = False
|
||||
|
||||
for line in lines:
|
||||
stripped = line.lstrip()
|
||||
|
||||
if _should_drop_line(stripped):
|
||||
continue
|
||||
|
||||
if stripped.startswith(".section"):
|
||||
if ".rdata" in stripped or ".data" in stripped:
|
||||
line = " .text"
|
||||
elif ".text$" in stripped or ".text.startup" in stripped:
|
||||
line = " .text"
|
||||
|
||||
if stripped.startswith(".data") or stripped.startswith(".rdata"):
|
||||
line = " .text"
|
||||
|
||||
if not align_inserted and (
|
||||
stripped.startswith(".text")
|
||||
or (stripped.startswith(".section") and ".text" in stripped)
|
||||
):
|
||||
cleaned.append(line)
|
||||
cleaned.append(ALIGN_STUB)
|
||||
align_inserted = True
|
||||
continue
|
||||
|
||||
cleaned.append(line)
|
||||
|
||||
if not align_inserted:
|
||||
cleaned.append(ALIGN_STUB)
|
||||
|
||||
return "\n".join(cleaned)
|
||||
@@ -0,0 +1,38 @@
|
||||
import sys
|
||||
|
||||
XOR_KEY = 0x5A
|
||||
|
||||
|
||||
def build_xor_stub(payload_len: int, key: int) -> bytes:
|
||||
if payload_len <= 0 or payload_len > 0xFFFFFFFF:
|
||||
raise ValueError("Payload length must be in 1..0xFFFFFFFF")
|
||||
|
||||
stub = bytearray([
|
||||
# 0: lea rsi, [rip+0x17] ; payload starts 30 bytes from stub start
|
||||
0x48, 0x8D, 0x35, 0x17, 0x00, 0x00, 0x00,
|
||||
# 7: mov ecx, <len> ; using placeholder bytes
|
||||
0xB9, 0x00, 0x00, 0x00, 0x00,
|
||||
# 12: mov al, <key> ; using placeholder bytes
|
||||
0xB0, 0x00,
|
||||
# 14: xor byte ptr [rsi], al
|
||||
0x30, 0x06,
|
||||
# 16: inc rsi
|
||||
0x48, 0xFF, 0xC6,
|
||||
# 19: loop decode_loop (back -7 bytes)
|
||||
0xE2, 0xF9,
|
||||
# 21: lea rax, [rip+0x2] ; payload again
|
||||
0x48, 0x8D, 0x05, 0x02, 0x00, 0x00, 0x00,
|
||||
# 28: jmp rax
|
||||
0xFF, 0xE0,
|
||||
])
|
||||
|
||||
# Patch length (little-endian) at offset 8
|
||||
stub[8:12] = payload_len.to_bytes(4, byteorder="little")
|
||||
# Patch XOR key at offset 13
|
||||
stub[13] = key & 0xFF
|
||||
|
||||
return bytes(stub)
|
||||
|
||||
|
||||
def xor_encode(payload: bytes, key: int) -> bytes:
|
||||
return bytes(b ^ (key & 0xFF) for b in payload)
|
||||
@@ -0,0 +1,85 @@
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from clean_asm import clean_asm_source
|
||||
from pe_extract import extract_text_section
|
||||
from encoder import XOR_KEY, build_xor_stub, xor_encode
|
||||
|
||||
def print_c_array(shellcode: bytes, varname: str = "shellcode"):
|
||||
print("")
|
||||
print("/* ================== C SHELLCODE ARRAY ================== */")
|
||||
print(f"unsigned char {varname}[] = {{")
|
||||
|
||||
line = " "
|
||||
for i, b in enumerate(shellcode):
|
||||
line += f"0x{b:02x}, "
|
||||
if (i + 1) % 16 == 0:
|
||||
print(line)
|
||||
line = " "
|
||||
if line.strip():
|
||||
print(line)
|
||||
|
||||
print("};")
|
||||
print(f"unsigned int {varname}_len = {len(shellcode)};")
|
||||
print("/* ======================================================== */")
|
||||
print("")
|
||||
|
||||
def do_clean(input_path: Path, output_path: Path) -> None:
|
||||
src = input_path.read_text(encoding="utf-8")
|
||||
cleaned = clean_asm_source(src)
|
||||
output_path.write_text(cleaned, encoding="utf-8")
|
||||
print(f"[+] Cleaned assembly written to {output_path}", file=sys.stderr)
|
||||
|
||||
|
||||
def do_extract(input_exe: Path, output_bin: Path) -> None:
|
||||
pe_bytes = input_exe.read_bytes()
|
||||
text_bytes, entry_offset = extract_text_section(pe_bytes)
|
||||
payload = text_bytes[entry_offset:]
|
||||
payload = payload.rstrip(b"\x00\x90\xcc")
|
||||
print(f"[+] Raw payload length: {len(payload)} bytes", file=sys.stderr)
|
||||
encoded = xor_encode(payload, XOR_KEY)
|
||||
stub = build_xor_stub(len(encoded), XOR_KEY)
|
||||
final_shellcode = stub + encoded
|
||||
|
||||
print(
|
||||
f"[+] Final shellcode (stub + encoded payload): {len(final_shellcode)} bytes",
|
||||
file=sys.stderr,
|
||||
)
|
||||
|
||||
output_bin.write_bytes(final_shellcode)
|
||||
print_c_array(final_shellcode, varname="shellcode_64")
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage:", file=sys.stderr)
|
||||
print(" handle_asm.py clean <input.s> <output.asm>", file=sys.stderr)
|
||||
print(" handle_asm.py extract <input.exe> <output.bin>", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
mode = sys.argv[1]
|
||||
|
||||
if mode == "clean":
|
||||
if len(sys.argv) != 4:
|
||||
print("Usage: handle_asm.py clean <input.s> <output.asm>", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
inp = Path(sys.argv[2])
|
||||
outp = Path(sys.argv[3])
|
||||
do_clean(inp, outp)
|
||||
return
|
||||
|
||||
if mode == "extract":
|
||||
if len(sys.argv) != 4:
|
||||
print("Usage: handle_asm.py extract <input.exe> <output.bin>", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
exe_path = Path(sys.argv[2])
|
||||
bin_path = Path(sys.argv[3])
|
||||
do_extract(exe_path, bin_path)
|
||||
return
|
||||
|
||||
print(f"Unknown mode: {mode}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,75 @@
|
||||
import struct
|
||||
import sys
|
||||
from typing import Tuple
|
||||
|
||||
|
||||
def extract_text_section(pe_bytes: bytes) -> Tuple[bytes, int]:
|
||||
if len(pe_bytes) < 0x100:
|
||||
raise ValueError("PE file too small")
|
||||
|
||||
e_lfanew = struct.unpack_from("<I", pe_bytes, 0x3C)[0]
|
||||
if pe_bytes[e_lfanew:e_lfanew + 4] != b"PE\0\0":
|
||||
raise ValueError("Invalid PE signature")
|
||||
|
||||
coff_offset = e_lfanew + 4
|
||||
(
|
||||
machine,
|
||||
number_of_sections,
|
||||
time_date_stamp,
|
||||
ptr_symtab,
|
||||
num_symbols,
|
||||
size_of_opt,
|
||||
characteristics,
|
||||
) = struct.unpack_from("<HHIIIHH", pe_bytes, coff_offset)
|
||||
|
||||
opt_offset = coff_offset + 20
|
||||
|
||||
entry_point_rva = struct.unpack_from("<I", pe_bytes, opt_offset + 16)[0]
|
||||
print(f"[*] Entry point RVA: 0x{entry_point_rva:x}", file=sys.stderr)
|
||||
|
||||
sect_offset = opt_offset + size_of_opt
|
||||
|
||||
text_section = None
|
||||
text_virtual_addr = None
|
||||
text_raw_ptr = None
|
||||
text_raw_size = None
|
||||
|
||||
for i in range(number_of_sections):
|
||||
off = sect_offset + i * 40
|
||||
name = pe_bytes[off:off + 8].rstrip(b"\x00").decode("ascii", errors="ignore")
|
||||
virtual_size, virtual_addr, raw_size, raw_ptr = struct.unpack_from(
|
||||
"<IIII", pe_bytes, off + 8
|
||||
)
|
||||
if name == ".text":
|
||||
text_virtual_addr = virtual_addr
|
||||
text_raw_ptr = raw_ptr
|
||||
text_raw_size = raw_size
|
||||
|
||||
if raw_ptr + raw_size > len(pe_bytes):
|
||||
raise ValueError(".text raw data out of range")
|
||||
|
||||
text_section = pe_bytes[raw_ptr:raw_ptr + raw_size]
|
||||
print(
|
||||
f"[*] .text section: VirtualAddr=0x{text_virtual_addr:x}, "
|
||||
f"RawPtr=0x{text_raw_ptr:x}, RawSize=0x{text_raw_size:x}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
break
|
||||
|
||||
if text_section is None:
|
||||
raise ValueError("No .text section found")
|
||||
|
||||
if text_virtual_addr is None or text_raw_size is None:
|
||||
raise ValueError("Invalid .text section layout")
|
||||
|
||||
if text_virtual_addr <= entry_point_rva < text_virtual_addr + text_raw_size:
|
||||
entry_offset = entry_point_rva - text_virtual_addr
|
||||
print(f"[*] Entry point offset in .text: 0x{entry_offset:x}", file=sys.stderr)
|
||||
else:
|
||||
print(
|
||||
f"[!] WARNING: Entry point RVA (0x{entry_point_rva:x}) is outside .text!",
|
||||
file=sys.stderr,
|
||||
)
|
||||
entry_offset = 0
|
||||
|
||||
return text_section, entry_offset
|
||||
Reference in New Issue
Block a user