adding source files

This commit is contained in:
husky
2025-12-11 08:30:45 -08:00
parent 8d2efe61f9
commit f5c4c45ba5
11 changed files with 478 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
build/*
tools/__pycache__
View File
View File
+32
View File
@@ -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
View File
@@ -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);
}
+10
View File
@@ -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);
+13
View File
@@ -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"
+82
View File
@@ -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)
+38
View File
@@ -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)
+85
View File
@@ -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()
+75
View File
@@ -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