今から7年ちょっと前にWindows ローカル グループのメンバーを取得するという記事を投稿したんですけど、この時はコマンドでnet localgroup (グループ名)
っていうコマンドを叩いてこの時の出力をテキストに出力してからそのテキストを読み込むという手法を使っていました。
それから時が流れ、6年半前にWindowsでsudoしたいという記事を投稿しました。こっちは最初、バッチファイルでやってたんですが、後々ShellExecute関数を使ったexe版も作りました。ただ、これでみんなが気になっていたのがUACが表示されるという点でした。というのも、標準ユーザー権限のアプリケーション上でShellExecuteを使って管理者タスクを作ろうとすると、権限レベルに差があるので昇格の処理が必要になるため、どうしてもUACの表示を回避できなかったんです。
そして3年前の夏、明月という屋号で個人事業を始めて、その時、自社開発アプリケーションの更新とかWindowsの更新プログラムみたいに標準ユーザー権限でもできるようにできないかと考え、それ以外にも色々と組み込んだ統合型ソフトウェア管理サービス「ソフトウェアマネージャー」というのの開発に取り掛かり、その過程でWindowsのアクティブユーザーのプロセスをWindowsサービスから立ち上げる方法というのを考え、その成果を2年前にWindowsサービスからサインインしているユーザーのプロセスを作成するという記事で出しました。
そしてついこの間、Windowsサービスの設計を考えてた時にふと思い浮かんだことがあったんです。
Windowsサービスからアクティブユーザーのプロセスを作成する方法で管理者権限のプロセスを作れたなら、この仕組みを使えばWindows用sudoでUACを表示させずに昇格させられるんじゃないか?
まあセキュリティ云々はとりあえず置いといて、理論的な話だけで言うのであれば回避可能なわけです。
ただ、管理者権限を持たないユーザーまで管理者に昇格できるのはまずいわけです。だってそれじゃあ何のために管理者権限を使える者を制約できるようにしているか分からないわけですからね。
じゃあどうするか?Administratorsグループのメンバーだけ使えるように制限すればいいわけです。幸いユーザーIDは簡単に取得できますし、あとはAdministratorsグループのメンバーさえリストアップできれば、そのリスト内のユーザーかを判定すれば昇格可能かどうかのチェックはできるんですから。
しかし、ここで1個問題があります。
どうやってAdministratorsグループのメンバー取得する?
はい。ここで一番の問題がやってきました。
いくらなんでもWindowsサービスでファイル作成してユーザー一覧取得して…なんてやるわけにはいきませんからね。
というかそんなことまだやってたら成長することを知らない残念なプログラマーとか思われかねないw
何よりあの方法、出力されているファイルに排他ロックがかかってたりすると読み込めないという問題が出てくるんです。
その問題を考えていた時にとあるサイトを見つけまして、私思ったんですよ。
NetAPI使えばワンチャン行けるんじゃね?
とりあえず実験しようってことで実験した結果できたので、そのコードをここで公開しようと思って投稿することにしました。
#include <Windows.h>
#include <LMaccess.h>
#include <string>
#include <vector>
class LocalGroupInformation {
private:
std::wstring Domain;
std::wstring Name;
std::wstring Comment;
public:
LocalGroupInformation() = default;
LocalGroupInformation(const std::wstring& LocalGroupName, const std::wstring& DomainName = {});
private:
LocalGroupInformation(const std::wstring& DomainName, const LOCALGROUP_INFO_1& Info);
public:
static std::vector<LocalGroupInformation> GetLocalGroups(const std::wstring& DomainName = {});
const std::wstring& GetName() const noexcept;
const std::wstring& GetComment() const noexcept;
std::vector<std::wstring> GetMembers() const;
};
struct Deleter {
void operator()(LPVOID Ptr) {
if (Ptr != NULL) {
NetApiBufferFree(Ptr);
Ptr = NULL;
}
}
};
LocalGroupInformation::LocalGroupInformation(const std::wstring& LocalGroupName, const std::wstring& DomainName) : Domain(DomainName), Name(LocalGroupName), Comment() {
std::unique_ptr<LOCALGROUP_INFO_1, Deleter> Info = NULL;
if (const auto result = NetLocalGroupGetInfo(std::wcslen(DomainName.c_str()) == 0 ? NULL : DomainName.c_str(), LocalGroupName.c_str(), 1, (LPBYTE*)&Info); result != NERR_Success) {
std::stringstream ss{};
ss << "Failed to get localgroup information : " << result;
throw std::runtime_error(ss.str());
}
this->Comment = std::wstring(Info->lgrpi1_comment);
}
LocalGroupInformation::LocalGroupInformation(const std::wstring& DomainName, const LOCALGROUP_INFO_1& Info)
: Domain(DomainName), Name(std::wstring(Info.lgrpi1_name)), Comment(std::wstring(Info.lgrpi1_comment)) {}
std::vector<LocalGroupInformation> LocalGroupInformation::GetLocalGroups(const std::wstring& DomainName) {
DWORD TotalEntities{}, PrefMaxLen{};
std::unique_ptr<LOCALGROUP_INFO_1[], Deleter> LocalGroupInfoBuffer = NULL;
if (const auto result = NetLocalGroupEnum(std::wcslen(DomainName.c_str()) == 0 ? NULL : DomainName.c_str(), 1, (LPBYTE*)&LocalGroupInfoBuffer, MAX_PREFERRED_LENGTH, &PrefMaxLen, &TotalEntities, NULL); NERR_Success != result) {
std::stringstream ss{};
ss << "Failed to get localgroup information : " << result;
throw std::runtime_error(ss.str());
}
std::vector<LocalGroupInformation> Ret{};
Ret.resize(PrefMaxLen);
for (DWORD i = 0; i < PrefMaxLen; i++) Ret.emplace_back(LocalGroupInformation(DomainName, LocalGroupInfoBuffer[i]));
return Ret;
}
const std::wstring& LocalGroupInformation::GetName() const noexcept { return this->Name; }
const std::wstring& LocalGroupInformation::GetComment() const noexcept { return this->Comment; }
std::vector<std::wstring> LocalGroupInformation::GetMembers() const {
DWORD TotalEntities{}, PrefMaxLen{};
std::unique_ptr<LOCALGROUP_MEMBERS_INFO_1[], Deleter> Buffer = NULL;
if (const auto result = NetLocalGroupGetMembers(std::wcslen(this->Domain.c_str()) == 0 ? NULL : this->Domain.c_str(), this->Name.c_str(), 1, (LPBYTE*)&Buffer, MAX_PREFERRED_LENGTH, &PrefMaxLen, &TotalEntities, NULL); NERR_Success != result) {
std::stringstream ss{};
ss << "Failed to get localgroup information : " << result;
throw std::runtime_error(ss.str());
}
std::vector<std::wstring> Ret{};
for (DWORD i = 0; i < PrefMaxLen; i++) Ret.emplace_back(std::wstring(Buffer[i].lgrmi1_name));
return Ret;
}
昔の私はポインターもそんなに得意ではなくて、結構メモリ開放漏れもあったし、WindowsのAPIもそんなに詳しかったわけではないのでコマンドをテキストファイルに出してやるってくらいしかできなかったですが、今となってはWindowsの闇にどっぷり浸かってAPIもめっちゃやってるからこそ思い浮かんだんでしょうね。
たぶんWindowsの闇に浸かりすぎたプログラマーの末路なんじゃないですかね…。