如何在Windows中获取已登录用户的SID

5

我需要获取已登录用户的字符串格式SID。我已经有了用户名,并尝试使用LookupAccountName来获取SID。这部分可以工作-我确实得到了一个SID,但它只是用户实际SID的部分匹配。

我不想要进程所有者的SID,因为该进程可能会被提升(模拟),而是我想要在运行该进程的地方登录的用户的SID。

代码需要使用非提升权限运行。

这是我目前的代码:

LPCTSTR wszAccName = TEXT("hardcoded username for testing");
LPTSTR wszDomainName = (LPTSTR)GlobalAlloc(GPTR, sizeof(TCHAR) * 1024);
DWORD cchDomainName = 1024;
SID_NAME_USE eSidType;
SID sid;
DWORD cbSid = 1024;
if (!LookupAccountName(NULL, wszAccName, &sid, &cbSid, wszDomainName, &cchDomainName, &eSidType)) {
    return GetLastError();
}

if (!ConvertSidToStringSid(&sid, &pszSidBuffer)) {
    return GetLastError();
}

这会产生一个类似于"S-1-5-21-1-1234567890-9-1000"的SID。但是用户的实际SID类似于"S-1-5-21-3214321539-1234567890-2233445522-1000"(根据进程所有者和HKEY_USERS下的注册表键)。 请注意,这些SID除了第5个和第7个组件不同之外,其他都相同,这两个组件只有1位数字,但应该更长。
我如何获取用户的实际/完整SID?
此外,我是一个完全的C/C++新手(上面的代码根本不适合生产)。我正在使用/NODEFAULTLIB以避免链接VC运行时库。对此很抱歉。

看起来你用这段代码获取的SID具有不同的域/本地ID。你是如何确定要与之进行比较的“用户实际SID”的?这台机器连接到域吗? - Cody Gray
@CodyGray 更新了问题。实际的SID是指我通过查看进程所有者(未显示代码)或查看用户注册表键HKEY_USERS下的键来获取的那个。 - Blaze
看起来WTSQueryUserToken会非常有帮助,因为我可以使用WTS函数获取SessionID(不要与SID混淆),然后从用户令牌中获取SID。然而,WTSQueryUserToken需要特权(即服务),而我需要在普通的非提升进程中运行它。 - Blaze
一位同事建议从explorer.exe的所有者获取SID。这在某种程度上是有效的,但它无法处理边缘情况,例如由不同用户重新启动资源管理器或同时有多个用户登录(因此存在多个资源管理器进程)。 - Blaze
1
可能可以从窗口站或桌面对象中提取您需要的SID,例如,用户可能是所有者。但我真的无法想象非提升代码关心已登录用户而不是代码正在运行的用户的任何合法原因。您能澄清一下您实际想要实现什么吗?也就是说,一旦获得了SID,您将如何处理它? - Harry Johnston
我正在开发一套用于安装程序中用户/管理员/提权操作的函数集。SID 可以用于访问 HKEY_USERS,或者仅用于比较进程所有者与已登录用户以检测模拟等情况。这可能会被多个开发人员用于其自己的用例,并不一定是他们想要实现目标的正确方式。 - Blaze
1个回答

4

你的代码没有为LookupAccountName()返回的SID提供一个适当大小的缓冲区。这会导致堆栈损坏和未定义的行为,这可能解释了为什么你没有得到预期的SID。(尽管我更怀疑你传递了错误的用户名或格式不正确的用户名。)

无论如何,为了修复最明显的问题,代码应该看起来更像这样:

#include <Windows.h>
#include <Sddl.h>

#include <stdio.h>

int main(int argc, char ** argv)
{
    LPCTSTR wszAccName = TEXT("domainname\\username");
    LPTSTR wszDomainName = (LPTSTR)GlobalAlloc(GPTR, sizeof(TCHAR) * 1024);
    DWORD cchDomainName = 1024;
    SID_NAME_USE eSidType;
    LPTSTR sidstring;
    char sid_buffer[1024];
    DWORD cbSid = 1024;
    SID * sid = (SID *)sid_buffer;

    if (!LookupAccountName(NULL, wszAccName, sid_buffer, &cbSid, wszDomainName, &cchDomainName, &eSidType)) {
        return GetLastError();
    }

    if (!ConvertSidToStringSid(sid, &sidstring)) {
        return GetLastError();
    }

    printf("%ws\n", sidstring);
    return 0;

}

当然,这不是正确的做法;您应该调用LookupAccountName()两次,一次确定缓冲区长度,然后第二次检索实际信息。但它展示了你做错了什么,并且足以用于测试目的。


谢谢!这个方法可行,看起来是我的缓冲区大小不正确导致了奇怪的结果(我使用的帐户名类似于“Dave-PC\Dave”,这是正确的)。 - Blaze

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接