Skip to content

Instantly share code, notes, and snippets.

@deadem
Created January 14, 2026 06:14
Show Gist options
  • Select an option

  • Save deadem/d470944a65ffbde5249faf35dd963378 to your computer and use it in GitHub Desktop.

Select an option

Save deadem/d470944a65ffbde5249faf35dd963378 to your computer and use it in GitHub Desktop.
gss / kerberos packet decode
#include <krb5.h>
#include "gss.h"
#include <fstream>
#include <iostream>
#include <string>
#include <vector>
#include <cstring>
#include <memory>
#include <cstdint>
#include <array>
#include <algorithm>
gss_server_state* gss_server_state_new() {
gss_server_state* state = (gss_server_state*)malloc(sizeof(gss_server_state));
state->username = NULL;
state->response = NULL;
state->targetname = NULL;
state->context_complete = false;
return state;
}
static gss_result* gss_success_result(int ret);
static gss_result* gss_error_result(OM_uint32 err_maj, OM_uint32 err_min);
static gss_result* gss_success_result(int ret) {
gss_result* result = (gss_result*)malloc(sizeof(gss_result));
result->code = ret;
result->message = NULL;
return result;
}
static gss_result* gss_error_result(OM_uint32 err_maj, OM_uint32 err_min) {
OM_uint32 maj_stat, min_stat;
OM_uint32 msg_ctx = 0;
gss_buffer_desc status_string;
char buf_maj[512];
char buf_min[512];
gss_result* result = nullptr;
do {
maj_stat = gss_display_status(
&min_stat, err_maj, GSS_C_GSS_CODE, GSS_C_NO_OID, &msg_ctx, &status_string);
if (GSS_ERROR(maj_stat))
break;
strncpy(buf_maj, (char*)status_string.value, sizeof(buf_maj));
gss_release_buffer(&min_stat, &status_string);
maj_stat = gss_display_status(
&min_stat, err_min, GSS_C_MECH_CODE, GSS_C_NULL_OID, &msg_ctx, &status_string);
if (!GSS_ERROR(maj_stat)) {
strncpy(buf_min, (char*)status_string.value, sizeof(buf_min));
gss_release_buffer(&min_stat, &status_string);
}
} while (!GSS_ERROR(maj_stat) && msg_ctx != 0);
result = (gss_result*)malloc(sizeof(gss_result));
result->code = AUTH_GSS_ERROR;
result->message = (char*)malloc(sizeof(char) * 1024 + 2);
sprintf(result->message, "%s: %s", buf_maj, buf_min);
return result;
}
static gss_result* gss_error_result_with_message(const char* message) {
gss_result* result = (gss_result*)malloc(sizeof(gss_result));
result->code = AUTH_GSS_ERROR;
result->message = strdup(message);
return result;
}
gss_result* authenticate_gss_server_init(const char* service, gss_server_state* state) {
OM_uint32 maj_stat;
OM_uint32 min_stat;
size_t service_len;
gss_buffer_desc name_token = GSS_C_EMPTY_BUFFER;
gss_result* ret = NULL;
state->context = GSS_C_NO_CONTEXT;
state->server_name = GSS_C_NO_NAME;
state->client_name = GSS_C_NO_NAME;
state->server_creds = GSS_C_NO_CREDENTIAL;
state->client_creds = GSS_C_NO_CREDENTIAL;
state->username = NULL;
state->targetname = NULL;
state->response = NULL;
// Server name may be empty which means we aren't going to create our own creds
service_len = std::strlen(service);
if (service_len != 0) {
// Import server name first
name_token.length = std::strlen(service);
name_token.value = (char*)service;
std::cout << "gss_import_name" << std::endl;
maj_stat = gss_import_name(
&min_stat, &name_token, GSS_C_NT_HOSTBASED_SERVICE, &state->server_name);
if (GSS_ERROR(maj_stat)) {
ret = gss_error_result(maj_stat, min_stat);
goto end;
}
std::cout << "gss_acquire_cred" << std::endl;
// Get credentials
maj_stat = gss_acquire_cred(&min_stat,
state->server_name,
GSS_C_INDEFINITE,
GSS_C_NO_OID_SET,
GSS_C_ACCEPT,
&state->server_creds,
NULL,
NULL);
if (GSS_ERROR(maj_stat)) {
ret = gss_error_result(maj_stat, min_stat);
goto end;
}
}
ret = gss_success_result(AUTH_GSS_COMPLETE);
end:
return ret;
}
template <typename T>
T read(const unsigned char *&ptr, size_t &size) {
size_t dataSize = sizeof(T);
if (dataSize > size) {
return {};
}
T result;
std::memcpy(&result, ptr, dataSize);
ptr += dataSize;
size -= dataSize;
return result;
}
#pragma pack(1)
struct FILETIME {
uint32_t m_lowDateTime;
uint32_t m_highDateTime;
};
void align(size_t align, size_t offset, const unsigned char *&ptrStream, size_t &sizeStream)
{
size_t shift = offset & align - 1;
if (align && shift) {
size_t seek = align - shift;
ptrStream += seek;
sizeStream -= seek;
}
}
std::string readString(const unsigned char *&ptr, size_t &size, const unsigned char *&ptrStream, size_t &sizeStream)
{
int length = read<uint16_t>(ptr, size);
int maxLength = read<uint16_t>(ptr, size);
int referent = read<uint32_t>(ptr, size);
if (referent == 0) {
return {};
}
// std::cout << "\nlen: " << length << " " << maxLength << " " << referent;
int total = read<uint32_t>(ptrStream, sizeStream);
int unused = read<uint32_t>(ptrStream, sizeStream);
int used = read<uint32_t>(ptrStream, sizeStream);
// std::cout << "\nused: " << total << " " << unused << " " << used;
std::string result;
for (int i = 0; i < used; ++i) {
wchar_t c = read<uint16_t>(ptrStream, sizeStream);
result += static_cast<char>(c);
}
return result;
}
std::vector<int> readGroupMembership(size_t count, const unsigned char *&ptr, size_t &size, const unsigned char *&ptrStream, size_t &sizeStream)
{
int referent = read<uint32_t>(ptr, size);
if (referent == 0) {
return {};
}
int conformance = read<uint32_t>(ptrStream, sizeStream);
if (conformance != count) {
// FATAL!
return {};
}
// std::cout << "\ngroups: ";
std::vector<int> result;
for (unsigned i = 0; i < count; ++i)
{
int relativeId = read<uint32_t>(ptrStream, sizeStream);
int attr = read<uint32_t>(ptrStream, sizeStream);
result.push_back(relativeId);
// std::cout << relativeId << " ";
}
return result;
}
std::string readSid(const unsigned char *&ptr, size_t &size, const unsigned char *&ptrStream, size_t &sizeStream)
{
int referent = read<uint32_t>(ptr, size);
if (referent == 0) {
return {};
}
int conformance = read<uint32_t>(ptrStream, sizeStream);
int revision = read<uint8_t>(ptrStream, sizeStream);
int subAuthorityCount = read<uint8_t>(ptrStream, sizeStream);
if (conformance != subAuthorityCount) {
std::cout << "\nSid fail!";
return {};
}
auto idAuthority = read<std::array<uint8_t, 6>>(ptrStream, sizeStream);
uint32_t authority = idAuthority[5];
// std::cout << "\nauth: " << (int)idAuthority[0] << (int)idAuthority[1] << (int)idAuthority[2] << (int)idAuthority[3] << (int)idAuthority[4] << (int)idAuthority[5];
std::string result = "S-";
result.append(std::to_string(revision)).append("-");
result.append(std::to_string(authority));
for (size_t i = 0; i < subAuthorityCount; ++i) {
result.append("-").append(std::to_string(read<uint32_t>(ptrStream, sizeStream)));
}
return result;
}
void read_pac_data(const unsigned char *ptr, size_t size)
{
const int static FIXED_PAC_SIZE = 236;
const unsigned char *ptrStart = ptr;
const unsigned char *ptrStream = ptr + FIXED_PAC_SIZE;
size_t sizeStream = size - FIXED_PAC_SIZE;
// header:
int version = read<unsigned char>(ptr, size);
int endianType = read<unsigned char>(ptr, size);
read<uint16_t>(ptr, size); // Header length
int filler = read<uint32_t>(ptr, size); // Data filler
int length = read<uint32_t>(ptr, size); // Object buffer length
read<uint32_t>(ptr, size); // Constructed type filler
if (endianType != 16) {
std::cout << "Error LE type" << std::endl;
}
if (length > size) {
std::cout << "Error: expected length is greater than available" << std::endl;
}
std::cout << "version: " << version << "\nLE: " << endianType << "\nFiller: " << filler << "\nSize: " << length;
int referent = read<uint32_t>(ptr, size);
if (referent == 0) {
std::cout << "Can't read PAC referent" << std::endl;
}
static_assert(sizeof(FILETIME) == 8, "FILETIME size mismatch");
read<FILETIME>(ptr, size); // Logon time
read<FILETIME>(ptr, size); // Logoff time
read<FILETIME>(ptr, size); // Kickoff time
read<FILETIME>(ptr, size); // Pwd last change time
read<FILETIME>(ptr, size); // Pwd can change time
read<FILETIME>(ptr, size); // Pwd must change time
align(sizeof(uint32_t), ptrStream - ptrStart, ptrStream, sizeStream);
std::string userName = readString(ptr, size, ptrStream, sizeStream);
align(sizeof(uint32_t), ptrStream - ptrStart, ptrStream, sizeStream);
std::string userDisplayName = readString(ptr, size, ptrStream, sizeStream);
align(sizeof(uint32_t), ptrStream - ptrStart, ptrStream, sizeStream);
readString(ptr, size, ptrStream, sizeStream); // Logon script
align(sizeof(uint32_t), ptrStream - ptrStart, ptrStream, sizeStream);
readString(ptr, size, ptrStream, sizeStream); // ProfilePath
align(sizeof(uint32_t), ptrStream - ptrStart, ptrStream, sizeStream);
readString(ptr, size, ptrStream, sizeStream); // Home directory
align(sizeof(uint32_t), ptrStream - ptrStart, ptrStream, sizeStream);
readString(ptr, size, ptrStream, sizeStream); // Home drive
std::cout << "\nusername: " << userName;
int logonCount = read<uint16_t>(ptr, size);
int badPasswordCount = read<uint16_t>(ptr, size);
int userId = read<uint32_t>(ptr, size);
int groupId = read<uint32_t>(ptr, size);
int groupCount = read<uint32_t>(ptr, size);
std::cout << "\ncount: " << logonCount << "\nbad: " << badPasswordCount;
std::cout << "\nuser: " << userId << "\ngroup: " << groupId << "\ngroupCount: " << groupCount;
align(sizeof(uint32_t), ptrStream - ptrStart, ptrStream, sizeStream);
auto groups = readGroupMembership(groupCount, ptr, size, ptrStream, sizeStream);
std::cout << "\ngroups: ";
std::sort(groups.begin(), groups.end());
for (const auto &rid : groups) {
std::cout << rid << " ";
}
read<uint32_t>(ptr, size); // User flags
read<uint64_t>(ptr, size); read<uint64_t>(ptr, size); // UserSessionKey
align(sizeof(uint32_t), ptrStream - ptrStart, ptrStream, sizeStream);
std::string serverName = readString(ptr, size, ptrStream, sizeStream);
align(sizeof(uint32_t), ptrStream - ptrStart, ptrStream, sizeStream);
std::string domainName = readString(ptr, size, ptrStream, sizeStream);
std::cout << "\nserver: " << serverName << "\ndomain: " << domainName;
align(sizeof(uint32_t), ptrStream - ptrStart, ptrStream, sizeStream);
std::string domainId = readSid(ptr, size, ptrStream, sizeStream);
std::cout << "\ndomainId: " << domainId;
read<uint64_t>(ptr, size); // Reserved1
read<uint32_t>(ptr, size); // UAC
read<uint32_t>(ptr, size); // SubAuth
read<FILETIME>(ptr, size); // Last sucess logon
read<FILETIME>(ptr, size); // Last failed logon
int failedLogonCount = read<uint32_t>(ptr, size);
std::cout << "\nfailedLogons: " << failedLogonCount;
read<uint32_t>(ptr, size); // Reserved3
int extraSidCount = read<uint32_t>(ptr, size);
read<uint32_t>(ptr, size); // readSidAttributes... referent
read<uint32_t>(ptr, size); // readResourceDomainId... referent
int resourceGroupCount = read<uint32_t>(ptr, size);
std::cout << "\nextraSidCount: " << extraSidCount << " rgroups: " << resourceGroupCount;
read<uint32_t>(ptr, size); // readResourceGroups... referent
std::cout << "\n>>>>>>>>>>>>>>>ptr: " << ptr - ptrStart << " ptrs: " << ptrStream - ptrStart;
// assert(FIXED_PAC_SIZE == ptr - ptrStart);
std::cout << std::endl;
}
gss_result* gssapi_obtain_pac_blob(gss_server_state* state)
{
gss_result* ret = NULL;
OM_uint32 maj_stat;
OM_uint32 min_stat;
gss_buffer_desc pac_name = { sizeof("urn:mspac:") - 1, (void *)"urn:mspac:" };
gss_buffer_desc pac_buffer = { 0, nullptr };
gss_buffer_desc pac_display_buffer = { 0, nullptr };
int more = -1;
int authenticated = false;
int complete = false;
std::cout << "name: " << state->client_name << "!" << std::endl;
maj_stat = gss_get_name_attribute(
&min_stat, state->client_name, &pac_name,
&authenticated, &complete,
&pac_buffer, &pac_display_buffer, &more);
bool fail = GSS_ERROR(maj_stat);
if (fail) {
/* The Heimdal OID for getting the PAC */
#define EXTRACT_PAC_AUTHZ_DATA_FROM_SEC_CONTEXT_OID_LENGTH 8
/* EXTRACTION OID AUTHZ ID */
#define EXTRACT_PAC_AUTHZ_DATA_FROM_SEC_CONTEXT_OID "\x2a\x85\x70\x2b\x0d\x03\x81\x00"
gss_OID_desc pac_data_oid;
pac_data_oid.elements = (void*)(EXTRACT_PAC_AUTHZ_DATA_FROM_SEC_CONTEXT_OID);
pac_data_oid.length = EXTRACT_PAC_AUTHZ_DATA_FROM_SEC_CONTEXT_OID_LENGTH;
gss_buffer_set_t set = GSS_C_NO_BUFFER_SET;
/* If we didn't have the routine to get a verified, validated
* PAC (supplied only by MIT at the time of writing), then try
* with the Heimdal OID (fetches the PAC directly and always
* validates) */
maj_stat = gss_inquire_sec_context_by_oid(
&min_stat, state->context,
&pac_data_oid, &set);
std::cout << "m: " << maj_stat << " k: " << GSS_S_UNAVAILABLE << std::endl;
}
if (GSS_ERROR(maj_stat)) {
ret = gss_error_result(maj_stat, min_stat);
goto end;
}
{
unsigned char *ptr = (unsigned char *)pac_buffer.value;
uint32_t numBuffers = *((int *)ptr);
if (numBuffers < 4) {
ret = gss_error_result_with_message("less than 4 PAC buffers");
goto end;
}
unsigned char *info_buffer = ptr + 4 + 4; // skip buffers and version
unsigned int PAC_INFO_BUFFER_SIZE = 4 + 4 + 8;
for (unsigned int i = 0; i < numBuffers; ++i, info_buffer += PAC_INFO_BUFFER_SIZE) {
uint32_t type = *((uint32_t *)(info_buffer + 0));
uint32_t size = *((uint32_t *)(info_buffer + 4));
uint64_t offset = *((uint64_t *)(info_buffer + 8));
// std::cout << "type: " << type << " size: " << size << " offset: " << offset << std::endl;
switch (type) {
case 1: //PAC_TYPE_LOGON_INFO:
read_pac_data(ptr + offset, size);
break;
default:
break;
}
}
}
ret = gss_success_result(AUTH_GSS_COMPLETE);
end:
return ret;
}
gss_result* authenticate_gss_server_step(gss_server_state* state, const char* challenge, size_t length) {
OM_uint32 maj_stat;
OM_uint32 min_stat;
gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
gss_name_t target_name = GSS_C_NO_NAME;
// int ret = AUTH_GSS_CONTINUE;
gss_result* ret = NULL;
size_t fix = 0;
challenge += fix;
length -= fix;
// Always clear out the old response
if (state->response != NULL) {
free(state->response);
state->response = NULL;
}
// If there is a challenge (data from the server) we need to give it to GSS
if (challenge && *challenge) {
input_token.value = (void *)challenge; // base64_decode(challenge, &len);
input_token.length = length;
} else {
ret = gss_error_result_with_message("No challenge parameter in request from client");
goto end;
}
std::cout << "gss_accept_sec_context" << std::endl;
maj_stat = gss_accept_sec_context(&min_stat,
&state->context,
state->server_creds,
&input_token,
GSS_C_NO_CHANNEL_BINDINGS,
&state->client_name,
NULL,
&output_token,
NULL,
NULL,
&state->client_creds);
if (GSS_ERROR(maj_stat)) {
ret = gss_error_result(maj_stat, min_stat);
goto end;
}
// Grab the server response to send back to the client
if (output_token.length) {
// state->response =
// base64_encode((const unsigned char*)output_token.value, output_token.length);
// ;
maj_stat = gss_release_buffer(&min_stat, &output_token);
}
std::cout << "gss_display_name" << std::endl;
// Get the user name
maj_stat = gss_display_name(&min_stat, state->client_name, &output_token, NULL);
if (GSS_ERROR(maj_stat)) {
ret = gss_error_result(maj_stat, min_stat);
goto end;
}
state->username = (char*)malloc(output_token.length + 1);
strncpy(state->username, (char*)output_token.value, output_token.length);
state->username[output_token.length] = 0;
{
std::string fulllogin{state->username};
std::string login, domain;
const auto at = fulllogin.find('@');
domain = login = fulllogin;
if (at != std::string::npos) {
login = fulllogin.substr(0, at);
domain = fulllogin.substr(at + 1);
}
std::cout << "parsed: " << domain << "\\" << login << std::endl;
}
std::cout << "state->username: " << state->username << std::endl;
std::cout << "gss_inquire_context" << std::endl;
// Get the target name if no server creds were supplied
if (state->server_creds == GSS_C_NO_CREDENTIAL) {
maj_stat = gss_inquire_context(
&min_stat, state->context, NULL, &target_name, NULL, NULL, NULL, NULL, NULL);
if (GSS_ERROR(maj_stat)) {
ret = gss_error_result(maj_stat, min_stat);
goto end;
}
// Free output token if necessary before reusing
if (output_token.length)
gss_release_buffer(&min_stat, &output_token);
std::cout << "gss_display_name" << std::endl;
maj_stat = gss_display_name(&min_stat, target_name, &output_token, NULL);
if (GSS_ERROR(maj_stat)) {
ret = gss_error_result(maj_stat, min_stat);
goto end;
}
state->targetname = (char*)malloc(output_token.length + 1);
strncpy(state->targetname, (char*)output_token.value, output_token.length);
state->targetname[output_token.length] = 0;
std::cout << "state->targetname: " << state->targetname << std::endl;
}
ret = gss_success_result(AUTH_GSS_COMPLETE);
state->context_complete = true;
end:
// if (target_name != GSS_C_NO_NAME)
// gss_release_name(&min_stat, &target_name);
// if (output_token.length)
// gss_release_buffer(&min_stat, &output_token);
// if (input_token.value)
// free(input_token.value);
return ret;
}
int main() {
std::ifstream input("application.bin", std::ios::binary );
std::vector<char> buffer(std::istreambuf_iterator<char>(input), {});
std::cout << "gss_server_state_new" << std::endl;
gss_server_state* server_state = gss_server_state_new();
{
std::cout << "authenticate_gss_server_init" << std::endl;
std::shared_ptr<gss_result> result(authenticate_gss_server_init("HTTP", server_state));
// must clean up state if we won't be using it, smart pointers won't help here unfortunately
// because we can't `release` a shared pointer.
if (result->code == AUTH_GSS_ERROR) {
std::cout << "Fail: " << result->message << std::endl;
// free(server_state);
return 1;
}
}
std::cout << "authenticate_gss_server_step" << std::endl;
{
std::shared_ptr<gss_result> result(authenticate_gss_server_step(server_state, buffer.data(), buffer.size()));
if (result->code == AUTH_GSS_ERROR) {
std::cout << "Fail: " << result->message << std::endl;
// free(server_state);
return 1;
}
}
std::cout << "username/realm: " << server_state->username << std::endl;
std::cout << "response: " << (server_state->response ? server_state->response : "<nullptr>") << std::endl;
std::cout << "targetname: " << (server_state->targetname ? server_state->targetname : "<nullptr>") << std::endl;
std::cout << "context_complete: " << server_state->context_complete << std::endl;
{
std::shared_ptr<gss_result> result(gssapi_obtain_pac_blob(server_state));
if (result->code == AUTH_GSS_ERROR) {
std::cout << "Fail: " << result->message << std::endl;
// free(server_state);
return 1;
}
}
std::cout << "STOP." << std::endl;
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment