Skip to content

Instantly share code, notes, and snippets.

@rkttu
Last active November 7, 2020 04:57
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rkttu/091840851cb0f8bea9bb12fef454f77f to your computer and use it in GitHub Desktop.
Save rkttu/091840851cb0f8bea9bb12fef454f77f to your computer and use it in GitHub Desktop.
KCD 2020 Online Code Sample {WSLHUB]

프로그래밍 방식으로 WSL 배포판 안의 사용자를 조회하는 방법

이 코드 샘플은 프로그래밍 방식으로 WSL 배포판 안의 사용자를 조회할 수 있는 방법을 설명합니다. Win32 API를 주로 사용하였고, 일부 편의를 위하여 C# 코드를 이용했지만, C++ 코드에서도 같은 기능을 구현할 수 있습니다.

프로그램 코드를 테스트해보기 위해서는 .NET 5 이상의 SDK를 설치해야 합니다. 그 다음, 이 Gist 리포지터리를 Git으로 체크아웃하고 dotnet run 명령으로 실행해봅니다.

// Requires .NET 5
using Microsoft.Win32;
using System;
using System.IO;
using System.Runtime.InteropServices;
using static stdio;
using static wslapi;
using static windows;
using Microsoft.Win32.SafeHandles;
using System.Text;
using var lxssKey = Registry.CurrentUser.OpenSubKey(
Path.Combine("SOFTWARE", "Microsoft", "Windows", "CurrentVersion", "Lxss"),
false);
foreach (var keyName in lxssKey.GetSubKeyNames())
{
if (!Guid.TryParse(keyName, out Guid parsedGuid))
continue;
using var distroKey = lxssKey.OpenSubKey(keyName);
var distroName = distroKey.GetValue("DistributionName", default(string)) as string;
if (string.IsNullOrWhiteSpace(distroName))
continue;
var basePath = Path.GetFullPath(distroKey.GetValue("BasePath", default(string)) as string);
var kernelCommandLine = (distroKey.GetValue("KernelCommandLine", default(string)) as string ?? string.Empty);
var stdin = GetStdHandle(STD_INPUT_HANDLE);
var stdout = GetStdHandle(STD_OUTPUT_HANDLE);
var stderr = GetStdHandle(STD_ERROR_HANDLE);
printf("==== [%s] ====\n", distroName);
printf("* Distro ID: %s\n", parsedGuid.ToString());
printf("* Base Path: %s\n", basePath);
printf("* Kernel Command Line: %s\n", string.Join(' ', kernelCommandLine));
var isRegistered = WslIsDistributionRegistered(distroName);
if (isRegistered)
{
var hr = WslGetDistributionConfiguration(
distroName,
out int distroVersion,
out int defaultUserId,
out WslDistributionFlags flags,
out IntPtr environmentVariables,
out int environmentVariableCount);
if (hr >= 0)
{
printf("* WSL Version: %d\n", distroVersion);
printf("* Default UID: %d\n", defaultUserId);
printf("* Flag: %x\n", (int)flags);
unsafe
{
byte*** lpEnvironmentVariables = (byte***)environmentVariables.ToPointer();
for (int i = 0; i < environmentVariableCount; i++)
{
byte** lpArray = lpEnvironmentVariables[i];
var content = Marshal.PtrToStringAnsi(new IntPtr(lpArray));
printf(" * Environment Variable [%d]: %s\n", i, content);
Marshal.FreeCoTaskMem(new IntPtr(lpArray));
}
Marshal.FreeCoTaskMem(new IntPtr(lpEnvironmentVariables));
}
SECURITY_ATTRIBUTES attributes = new SECURITY_ATTRIBUTES
{
lpSecurityDescriptor = IntPtr.Zero,
bInheritHandle = true,
};
attributes.nLength = Marshal.SizeOf(attributes);
if (CreatePipe(out IntPtr readPipe, out IntPtr writePipe, ref attributes, 0))
{
hr = WslLaunch(distroName, "cat /etc/passwd", false, stdin, writePipe, stderr, out IntPtr child);
if (hr >= 0)
{
WaitForSingleObject(child, INFINITE);
if ((GetExitCodeProcess(child, out int exitCode) == false) || (exitCode != 0))
{
hr = E_INVALIDARG;
}
var processClosed = CloseHandle(child);
if (hr >= 0)
{
printf("* Fetching user names from `%s`.\n", distroName);
var length = 65536;
var buffer = Marshal.AllocHGlobal(length);
FillMemory(buffer, length, 0x00);
var read = 0;
var readFileResult = ReadFile(readPipe, buffer, length - 1, out read, IntPtr.Zero);
var lastError = Marshal.GetLastWin32Error();
var passwdContents = new StringBuilder();
passwdContents.Append(Marshal.PtrToStringAnsi(buffer, read));
Marshal.FreeHGlobal(buffer);
foreach (var eachLine in passwdContents.ToString().Split(
new char[] { '\r', '\n', },
StringSplitOptions.RemoveEmptyEntries))
{
StringBuilder
userName = new StringBuilder(),
comment = new StringBuilder(),
homedir = new StringBuilder(),
shell = new StringBuilder();
if (sscanf(eachLine, "%[^:]:%*[^:]:%u:%u:%[^:]:%[^:]:%[^:]",
userName, out int uid, out int gid, comment, homedir, shell) > 0)
{
if (1000 <= uid && uid <= 60000)
{
printf(" << %s >>\n", userName.ToString());
printf(" * UID: %d\n", uid);
printf(" * GID: %d\n", gid);
printf(" * Comment: %s\n", comment.ToString());
printf(" * Home Directory: %s\n", homedir.ToString());
printf(" * Shell: %s\n", shell.ToString());
printf("\n");
}
}
}
}
else
printf("* Cannot obtain passwd contents: 0x%08x\n", hr);
}
else
printf("* Cannot launch WSL process: 0x%08x\n", hr);
CloseHandle(readPipe);
CloseHandle(writePipe);
}
else
printf("* Cannot create pipe for I/O.\n");
}
else
printf("* Cannot obtain detail information: 0x%08x\n", hr);
}
printf("\n");
}
using System;
using System.Runtime.InteropServices;
using System.Text;
static class stdio
{
[DllImport("msvcrt.dll",
CallingConvention = CallingConvention.Cdecl,
CharSet = CharSet.Ansi,
ExactSpelling = true)]
public static extern int printf(string fmt);
[DllImport("msvcrt.dll",
CallingConvention = CallingConvention.Cdecl,
CharSet = CharSet.Ansi,
ExactSpelling = true)]
public static extern int printf(string fmt, int arg0);
[DllImport("msvcrt.dll",
CallingConvention = CallingConvention.Cdecl,
CharSet = CharSet.Ansi,
ExactSpelling = true)]
public static extern int printf(string fmt, string arg0);
[DllImport("msvcrt.dll",
CallingConvention = CallingConvention.Cdecl,
CharSet = CharSet.Ansi,
ExactSpelling = true)]
public static extern int printf(string fmt, int arg0, string arg1);
[DllImport("msvcrt.dll",
CallingConvention = CallingConvention.Cdecl,
CharSet = CharSet.Ansi,
ExactSpelling = true)]
public static extern int sscanf(string str, string fmt,
StringBuilder arg0,
[MarshalAs(UnmanagedType.U4)] out int arg1,
[MarshalAs(UnmanagedType.U4)] out int arg2,
StringBuilder arg3,
StringBuilder arg4,
StringBuilder arg5);
}
using System;
using System.Runtime.InteropServices;
static class windows
{
[DllImport("kernel32.dll",
CallingConvention = CallingConvention.Winapi,
SetLastError = false,
EntryPoint = "RtlFillMemory",
CharSet = CharSet.None,
ExactSpelling = true)]
public static extern void FillMemory(
IntPtr destination,
[MarshalAs(UnmanagedType.U4)] int length,
byte fill);
[Serializable, StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
[MarshalAs(UnmanagedType.U4)]
public int nLength;
public IntPtr lpSecurityDescriptor;
[MarshalAs(UnmanagedType.Bool)]
public bool bInheritHandle;
}
[DllImport("kernel32.dll",
CallingConvention = CallingConvention.Winapi,
SetLastError = true,
CharSet = CharSet.None,
ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CreatePipe(
out IntPtr hReadPipe,
out IntPtr hWritePipe,
ref SECURITY_ATTRIBUTES lpPipeAttributes,
[MarshalAs(UnmanagedType.U4)] int nSize);
[DllImport("kernel32.dll",
CallingConvention = CallingConvention.Winapi,
SetLastError = true,
CharSet = CharSet.None,
ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool ReadFile(
IntPtr hFile,
IntPtr lpBuffer,
[MarshalAs(UnmanagedType.U4)] int nNumberOfBytesToRead,
[MarshalAs(UnmanagedType.U4)] out int lpNumberOfBytesRead,
IntPtr lpOverlapped);
public static readonly int
E_INVALIDARG = unchecked((int)0x80070057);
public static readonly int
STD_INPUT_HANDLE = -10,
STD_OUTPUT_HANDLE = -11,
STD_ERROR_HANDLE = -12;
[DllImport("kernel32.dll",
CallingConvention = CallingConvention.Winapi,
SetLastError = true,
CharSet = CharSet.None,
ExactSpelling = true)]
public static extern IntPtr GetStdHandle(
[MarshalAs(UnmanagedType.U4)] int nStdHandle);
public static readonly int
INFINITE = unchecked((int)0xFFFFFFFF);
[DllImport("kernel32.dll",
CallingConvention = CallingConvention.Winapi,
SetLastError = true,
CharSet = CharSet.None,
ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.U4)]
public static extern int WaitForSingleObject(
IntPtr hHandle,
[MarshalAs(UnmanagedType.U4)] int dwMilliseconds);
public static readonly int
WAIT_ABANDONED = 0x00000080,
WAIT_OBJECT_0 = 0x00000000,
WAIT_TIMEOUT = 0x00000102,
WAIT_FAILED = unchecked((int)0xFFFFFFFF);
[DllImport("kernel32.dll",
CallingConvention = CallingConvention.Winapi,
SetLastError = true,
CharSet = CharSet.None,
ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetExitCodeProcess(
IntPtr hProcess,
[MarshalAs(UnmanagedType.U4)] out int lpExitCode);
[DllImport("kernel32.dll",
CallingConvention = CallingConvention.Winapi,
SetLastError = true,
CharSet = CharSet.None,
ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CloseHandle(IntPtr hObject);
}
using System;
using System.Runtime.InteropServices;
static class wslapi
{
[Flags, Serializable]
public enum WslDistributionFlags
{
None = 0x0,
EnableInterop = 0x1,
AppendNtPath = 0x2,
EnableDriveMouting = 0x4,
}
[DllImport("wslapi.dll",
CallingConvention = CallingConvention.Winapi,
CharSet = CharSet.Unicode,
ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool WslIsDistributionRegistered(
string distributionName);
[DllImport("wslapi.dll",
CallingConvention = CallingConvention.Winapi,
CharSet = CharSet.Unicode,
ExactSpelling = true,
PreserveSig = true)]
[return: MarshalAs(UnmanagedType.U4)]
public static extern int WslGetDistributionConfiguration(
string distributionName,
[MarshalAs(UnmanagedType.I4)] out int distributionVersion,
[MarshalAs(UnmanagedType.I4)] out int defaultUID,
[MarshalAs(UnmanagedType.I4)] out WslDistributionFlags wslDistributionFlags,
out IntPtr defaultEnvironmentVariables,
[MarshalAs(UnmanagedType.I4)] out int defaultEnvironmentVariableCount);
[DllImport("wslapi.dll",
CallingConvention = CallingConvention.Winapi,
CharSet = CharSet.Unicode,
ExactSpelling = true,
PreserveSig = true)]
[return: MarshalAs(UnmanagedType.U4)]
public static extern int WslLaunch(
string distributionName,
string command,
bool useCurrentWorkingDirectory,
IntPtr stdIn,
IntPtr stdOut,
IntPtr stdErr,
out IntPtr process);
}
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<StartupObject></StartupObject>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Win32.Registry" Version="4.7.0" />
</ItemGroup>
</Project>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment