#define WIN32_NO_STATUS
#include <windows.h>
#undef WIN32_NO_STATUS
#include <ntstatus.h>
#include <winternl.h>
#include <ntsecapi.h>
#include <stdio.h>
#include <stdlib.h>

#define SAM_SERVER_CONNECT            0x0001
#define SAM_SERVER_LOOKUP_DOMAIN      0x0020
#define SAM_SERVER_ENUMERATE_DOMAINS  0x0010

#define DOMAIN_LOOKUP                 0x0200
#define DOMAIN_LIST_ACCOUNTS          0x0100

#define USER_READ_GENERAL             0x0008
#define USER_READ_LOGON               0x0001
#define USER_READ_ACCOUNT             0x0020

#define UserAllInformation            21

typedef PVOID SAMPR_HANDLE;
typedef SAMPR_HANDLE *PSAMPR_HANDLE;

typedef struct _SAM_RID_ENUM {
    ULONG              RelativeId;
    LSA_UNICODE_STRING Name;
} SAM_RID_ENUM, *PSAM_RID_ENUM;

typedef struct _USER_ALL_INFO {
    LARGE_INTEGER      LastLogon;
    LARGE_INTEGER      LastLogoff;
    LARGE_INTEGER      PasswordLastSet;
    LARGE_INTEGER      AccountExpires;
    LARGE_INTEGER      PasswordCanChange;
    LARGE_INTEGER      PasswordMustChange;
    LSA_UNICODE_STRING UserName;
} USER_ALL_INFO, *PUSER_ALL_INFO;

typedef NTSTATUS (NTAPI *fnSamConnect)(
    PLSA_UNICODE_STRING ServerName, PSAMPR_HANDLE ServerHandle,
    ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes);
typedef NTSTATUS (NTAPI *fnSamOpenDomain)(
    SAMPR_HANDLE ServerHandle, ACCESS_MASK DesiredAccess,
    PSID DomainId, PSAMPR_HANDLE DomainHandle);
typedef NTSTATUS (NTAPI *fnSamEnumerateUsersInDomain)(
    SAMPR_HANDLE DomainHandle, PULONG EnumerationContext,
    ULONG UserAccountControl, PVOID *Buffer,
    ULONG PreferredMaximumLength, PULONG CountReturned);
typedef NTSTATUS (NTAPI *fnSamOpenUser)(
    SAMPR_HANDLE DomainHandle, ACCESS_MASK DesiredAccess,
    ULONG UserId, PSAMPR_HANDLE UserHandle);
typedef NTSTATUS (NTAPI *fnSamQueryInformationUser)(
    SAMPR_HANDLE UserHandle, ULONG UserInformationClass, PVOID *Buffer);
typedef NTSTATUS (NTAPI *fnSamCloseHandle)(SAMPR_HANDLE SamHandle);
typedef NTSTATUS (NTAPI *fnSamFreeMemory)(PVOID Buffer);

static fnSamConnect               SamConnect;
static fnSamOpenDomain            SamOpenDomain;
static fnSamEnumerateUsersInDomain SamEnumerateUsersInDomain;
static fnSamOpenUser              SamOpenUser;
static fnSamQueryInformationUser  SamQueryInformationUser;
static fnSamCloseHandle           SamCloseHandle;
static fnSamFreeMemory            SamFreeMemory;

static void InitLsaString(PLSA_UNICODE_STRING d, PWSTR s)
{
    size_t n = s ? wcslen(s) : 0;
    d->Length        = (USHORT)(n * sizeof(WCHAR));
    d->MaximumLength = (USHORT)((n + 1) * sizeof(WCHAR));
    d->Buffer        = s;
}

static void PrintU(const LSA_UNICODE_STRING *u)
{
    int wlen, n;
    char *b;
    if (!u || !u->Buffer || !u->Length) { printf("(null)"); return; }
    wlen = (int)(u->Length / sizeof(WCHAR));
    n = WideCharToMultiByte(CP_UTF8, 0, u->Buffer, wlen, NULL, 0, NULL, NULL);
    b = (char *)malloc((size_t)n + 1);
    if (!b) return;
    WideCharToMultiByte(CP_UTF8, 0, u->Buffer, wlen, b, n, NULL, NULL);
    b[n] = '\0';
    printf("%s", b);
    free(b);
}

static PSID BuildAccountSid(PSID domainSid, ULONG rid)
{
    DWORD len = GetLengthSid(domainSid) + sizeof(DWORD);
    PSID full = (PSID)malloc(len);
    UCHAR *count;
    if (!full) return NULL;
    if (!CopySid(len, full, domainSid)) { free(full); return NULL; }
    count = GetSidSubAuthorityCount(full);
    *GetSidSubAuthority(full, *count) = rid;
    *count = (UCHAR)(*count + 1);
    return full;
}

static void PrintLastLogon(LARGE_INTEGER ll)
{
    FILETIME ft;
    SYSTEMTIME st;
    if (ll.QuadPart == 0) {
        printf("never (0)   <-- no authentication history (possible honeypot)");
        return;
    }
    ft.dwLowDateTime  = ll.LowPart;
    ft.dwHighDateTime = (DWORD)ll.HighPart;
    if (FileTimeToSystemTime(&ft, &st)) {
        printf("%04d-%02d-%02d %02d:%02d:%02dZ",
               st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond);
    } else {
        printf("0x%08lx%08lx", (unsigned long)ll.HighPart, (unsigned long)ll.LowPart);
    }
}

static void Fail(const char *what, NTSTATUS st)
{
    fprintf(stderr, "[-] %s failed: NTSTATUS=0x%08lx (Win32=%lu)\n",
            what, (unsigned long)st, (unsigned long)LsaNtStatusToWinError(st));
}

int main(int argc, char **argv)
{
    wchar_t target[512];
    LSA_OBJECT_ATTRIBUTES loa;
    LSA_UNICODE_STRING sysName, samServerName;
    LSA_HANDLE policy = NULL;
    POLICY_DNS_DOMAIN_INFO *dns = NULL;
    PSID domainSid = NULL;
    HMODULE hSam;

    SAMPR_HANDLE serverH = NULL, domainH = NULL;
    OBJECT_ATTRIBUTES samOA;
    ULONG enumCtx = 0, returned = 0, total = 0, honeypots = 0;
    NTSTATUS st;

    if (argc < 2) {
        fprintf(stderr, "usage: %s <dc-hostname-or-ip>\n", argv[0]);
        return 1;
    }

    _snwprintf(target, 512, L"\\\\%hs", argv[1]);
    target[511] = L'\0';

    hSam = LoadLibraryW(L"samlib.dll");
    if (!hSam) { fprintf(stderr, "[-] cannot load samlib.dll\n"); return 1; }
    SamConnect                = (fnSamConnect)               GetProcAddress(hSam, "SamConnect");
    SamOpenDomain             = (fnSamOpenDomain)            GetProcAddress(hSam, "SamOpenDomain");
    SamEnumerateUsersInDomain = (fnSamEnumerateUsersInDomain)GetProcAddress(hSam, "SamEnumerateUsersInDomain");
    SamOpenUser               = (fnSamOpenUser)              GetProcAddress(hSam, "SamOpenUser");
    SamQueryInformationUser   = (fnSamQueryInformationUser)  GetProcAddress(hSam, "SamQueryInformationUser");
    SamCloseHandle            = (fnSamCloseHandle)           GetProcAddress(hSam, "SamCloseHandle");
    SamFreeMemory             = (fnSamFreeMemory)            GetProcAddress(hSam, "SamFreeMemory");
    if (!SamConnect || !SamOpenDomain || !SamEnumerateUsersInDomain ||
        !SamOpenUser || !SamQueryInformationUser || !SamCloseHandle || !SamFreeMemory) {
        fprintf(stderr, "[-] missing samlib.dll export\n");
        return 1;
    }

    ZeroMemory(&loa, sizeof(loa));
    InitLsaString(&sysName, target);
    st = LsaOpenPolicy(&sysName, &loa,
                       POLICY_VIEW_LOCAL_INFORMATION | POLICY_LOOKUP_NAMES,
                       &policy);
    if (!NT_SUCCESS(st)) { Fail("LsaOpenPolicy", st); return 1; }

    st = LsaQueryInformationPolicy(policy, PolicyDnsDomainInformation, (PVOID *)&dns);
    if (!NT_SUCCESS(st)) {
        Fail("LsaQueryInformationPolicy", st);
        LsaClose(policy);
        return 1;
    }
    if (dns == NULL || dns->Sid == NULL) {
        fprintf(stderr, "[-] host returned no domain SID -- target is not a "
                        "domain controller (workgroup / standalone host?)\n");
        if (dns) LsaFreeMemory(dns);
        LsaClose(policy);
        return 1;
    }
    domainSid = dns->Sid;
    printf("[*] target DC      : %ls\n", target);
    printf("[*] domain (NetBIOS): "); PrintU(&dns->Name);          printf("\n");
    printf("[*] domain (DNS)    : "); PrintU(&dns->DnsDomainName); printf("\n\n");

    ZeroMemory(&samOA, sizeof(samOA));
    samOA.Length = sizeof(samOA);
    InitLsaString(&samServerName, target);
    st = SamConnect(&samServerName, &serverH,
                    SAM_SERVER_CONNECT | SAM_SERVER_LOOKUP_DOMAIN | SAM_SERVER_ENUMERATE_DOMAINS,
                    &samOA);
    if (!NT_SUCCESS(st)) { Fail("SamConnect", st); goto cleanup; }

    st = SamOpenDomain(serverH, DOMAIN_LOOKUP | DOMAIN_LIST_ACCOUNTS, domainSid, &domainH);
    if (!NT_SUCCESS(st)) { Fail("SamOpenDomain", st); goto cleanup; }

    printf("%-8s %-28s %-28s %s\n", "RID", "sAMAccountName (SAMR)", "resolved (LSAT)", "lastLogon");
    printf("-------- ---------------------------- ---------------------------- ---------\n");

    do {
        PSAM_RID_ENUM list = NULL;
        ULONG i;
        st = SamEnumerateUsersInDomain(domainH, &enumCtx, 0,
                                       (PVOID *)&list, 1000, &returned);
        if (!NT_SUCCESS(st) && st != STATUS_MORE_ENTRIES) {
            Fail("SamEnumerateUsersInDomain", st);
            break;
        }
        for (i = 0; i < returned; i++) {
            ULONG rid = list[i].RelativeId;
            PSID  sid = BuildAccountSid(domainSid, rid);
            SAMPR_HANDLE userH = NULL;
            PUSER_ALL_INFO uai = NULL;

            printf("%-8lu ", (unsigned long)rid);

            {
                int pad = 28;
                int len = (int)(list[i].Name.Length / sizeof(WCHAR));
                PrintU(&list[i].Name);
                while (len++ < pad) putchar(' ');
                putchar(' ');
            }

            if (sid) {
                PLSA_REFERENCED_DOMAIN_LIST refDoms = NULL;
                PLSA_TRANSLATED_NAME names = NULL;
                NTSTATUS lst = LsaLookupSids(policy, 1, &sid, &refDoms, &names);
                if (NT_SUCCESS(lst) && names && names[0].Use != SidTypeUnknown) {
                    int pad = 28, len = (int)(names[0].Name.Length / sizeof(WCHAR));
                    PrintU(&names[0].Name);
                    while (len++ < pad) putchar(' ');
                    putchar(' ');
                } else {
                    printf("%-28s ", "(unresolved)");
                }
                if (names)   LsaFreeMemory(names);
                if (refDoms) LsaFreeMemory(refDoms);
                free(sid);
            } else {
                printf("%-28s ", "(sid error)");
            }

            st = SamOpenUser(domainH,
                             USER_READ_GENERAL | USER_READ_LOGON | USER_READ_ACCOUNT,
                             rid, &userH);
            if (NT_SUCCESS(st)) {
                st = SamQueryInformationUser(userH, UserAllInformation, (PVOID *)&uai);
                if (NT_SUCCESS(st) && uai) {
                    PrintLastLogon(uai->LastLogon);
                    if (uai->LastLogon.QuadPart == 0) honeypots++;
                    SamFreeMemory(uai);
                } else {
                    printf("query failed (0x%08lx)", (unsigned long)st);
                }
                SamCloseHandle(userH);
            } else {
                printf("open failed (0x%08lx)", (unsigned long)st);
            }
            putchar('\n');
            total++;
        }
        SamFreeMemory(list);
    } while (st == STATUS_MORE_ENTRIES);

    printf("\n[*] %lu user objects enumerated, %lu with lastLogon == 0 (decoy candidates)\n",
           (unsigned long)total, (unsigned long)honeypots);

cleanup:
    if (domainH) SamCloseHandle(domainH);
    if (serverH) SamCloseHandle(serverH);
    if (dns)     LsaFreeMemory(dns);
    if (policy)  LsaClose(policy);
    FreeLibrary(hSam);
    return 0;
}
