using System; using System.Drawing; using System.IO; using System.Security.Cryptography; using System.Runtime.InteropServices; using System.Text; using System.Windows.Forms; using Microsoft.Win32; namespace WindowsKeyInfo { internal static class Program { [STAThread] private static int Main(string[] args) { // 관리자 권한으로 다시 실행된 경우: 사용자 지정 암호(해시)만 레지스트리에 저장하고 종료 if (args.Length == 3 && args[0] == PasswordStore.SetPasswordArg) { return PasswordStore.SaveFromElevatedArgs(args[1], args[2]); } Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); // 처음 알림창: 입력칸 없이 "Windows Key" + [확인]만 보이고, 키보드 입력은 숨은 칸으로 받음 bool fullMode; using (var unlock = new UnlockDialog()) { if (unlock.ShowDialog() != DialogResult.OK) return 0; // X / Esc → 종료 fullMode = PasswordStore.Verify(unlock.Password); } Application.Run(new MainForm(fullMode)); return 0; } } /// 마스터 암호 관리 (내장 암호 1개 + 사용자 지정 암호 1개, PBKDF2-SHA256 해시) internal static class PasswordStore { public const string SetPasswordArg = "--set-password"; private const string RegistryPath = @"SOFTWARE\WindowsKeyInfo"; private const string SaltValue = "PasswordSalt"; private const string HashValue = "PasswordHash"; private const string IterationsValue = "PasswordIterations"; private const int Iterations = 100000; private const int SaltSize = 16; private const int HashSize = 32; // 내장 마스터 암호 (변경 불가) — 원문 대신 솔트와 해시만 보관 private static readonly byte[] BuiltInSalt = Convert.FromBase64String("nkeJJJqcUJ+4KYuNEcYFiQ=="); private static readonly byte[] BuiltInHash = Convert.FromBase64String("9Y8DuoR8YwDoIak79Wt0db3truf7+Z0rMPhEPY2c6og="); /// 내장 암호 또는 사용자 지정 암호 중 하나와 일치하면 true public static bool Verify(string password) { if (string.IsNullOrEmpty(password)) return false; if (Matches(password, BuiltInSalt, BuiltInHash, Iterations)) return true; try { using (RegistryKey key = OpenBase().OpenSubKey(RegistryPath)) { if (key == null) return false; var salt = key.GetValue(SaltValue) as byte[]; var hash = key.GetValue(HashValue) as byte[]; object iterObj = key.GetValue(IterationsValue); int iter = iterObj is int ? (int)iterObj : Iterations; if (salt == null || hash == null || iter <= 0) return false; return Matches(password, salt, hash, iter); } } catch { return false; } } /// 새 사용자 지정 암호 저장 (관리자 권한으로 자기 자신을 다시 실행, 해시만 전달) public static void ChangeUserPassword(IWin32Window owner, string newPassword, string caption) { byte[] salt = new byte[SaltSize]; using (var rng = RandomNumberGenerator.Create()) rng.GetBytes(salt); byte[] hash = Derive(newPassword, salt, Iterations); string exe = System.Reflection.Assembly.GetExecutingAssembly().Location; var psi = new System.Diagnostics.ProcessStartInfo(exe, SetPasswordArg + " " + Convert.ToBase64String(salt) + " " + Convert.ToBase64String(hash)) { UseShellExecute = true, Verb = "runas" // 관리자 권한 요청 (UAC 창) }; try { using (var p = System.Diagnostics.Process.Start(psi)) { p.WaitForExit(); if (p.ExitCode == 0) { MessageBox.Show(owner, "암호를 변경했습니다.", caption, MessageBoxButtons.OK, MessageBoxIcon.Information); } else { MessageBox.Show(owner, "암호를 저장하지 못했습니다. (오류 코드: " + p.ExitCode + ")", caption, MessageBoxButtons.OK, MessageBoxIcon.Error); } } } catch (System.ComponentModel.Win32Exception ex) when (ex.NativeErrorCode == 1223) { MessageBox.Show(owner, "관리자 권한 요청이 취소되었습니다.\n암호 변경에는 관리자 권한이 필요합니다.", caption, MessageBoxButtons.OK, MessageBoxIcon.Information); } catch (Exception ex) { MessageBox.Show(owner, "암호 변경을 실행하지 못했습니다.\n\n" + ex.Message, caption, MessageBoxButtons.OK, MessageBoxIcon.Error); } } /// 관리자 권한 프로세스에서 호출: HKLM에 솔트/해시 기록. 성공 0, 실패 1/2 public static int SaveFromElevatedArgs(string saltB64, string hashB64) { byte[] salt, hash; try { salt = Convert.FromBase64String(saltB64); hash = Convert.FromBase64String(hashB64); } catch { return 2; } if (salt.Length != SaltSize || hash.Length != HashSize) return 2; try { using (RegistryKey key = OpenBase().CreateSubKey(RegistryPath)) { key.SetValue(SaltValue, salt, RegistryValueKind.Binary); key.SetValue(HashValue, hash, RegistryValueKind.Binary); key.SetValue(IterationsValue, Iterations, RegistryValueKind.DWord); } return 0; } catch { return 1; } } private static RegistryKey OpenBase() { RegistryView view = Environment.Is64BitOperatingSystem ? RegistryView.Registry64 : RegistryView.Default; return RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, view); } private static byte[] Derive(string password, byte[] salt, int iterations) { using (var kdf = new Rfc2898DeriveBytes(Encoding.UTF8.GetBytes(password), salt, iterations, HashAlgorithmName.SHA256)) { return kdf.GetBytes(HashSize); } } private static bool Matches(string password, byte[] salt, byte[] expected, int iterations) { byte[] actual = Derive(password, salt, iterations); if (actual.Length != expected.Length) return false; int diff = 0; for (int i = 0; i < actual.Length; i++) diff |= actual[i] ^ expected[i]; // 고정 시간 비교 return diff == 0; } } /// 암호 입력칸용: 들어가면 영문 입력 + Caps Lock 끔, 나가면(창 닫힘 포함) 원래 입력 방식으로 복구 internal static class EnglishInput { [DllImport("imm32.dll")] private static extern IntPtr ImmGetContext(IntPtr hWnd); [DllImport("imm32.dll")] private static extern bool ImmReleaseContext(IntPtr hWnd, IntPtr hIMC); [DllImport("imm32.dll")] private static extern bool ImmGetConversionStatus(IntPtr hIMC, out int conversion, out int sentence); [DllImport("imm32.dll")] private static extern bool ImmSetConversionStatus(IntPtr hIMC, int conversion, int sentence); [DllImport("user32.dll")] private static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, UIntPtr dwExtraInfo); private const int ImeCmodeNative = 0x0001; // 한글 모드 비트 private const byte VkCapital = 0x14; private const uint KeyEventFKeyUp = 0x0002; public static void Attach(TextBox box) { InputLanguage saved = null; bool active = false; box.ImeMode = ImeMode.Alpha; Action restore = () => { if (!active) return; active = false; try { if (saved != null && !saved.Equals(InputLanguage.CurrentInputLanguage)) InputLanguage.CurrentInputLanguage = saved; } catch { } }; box.Enter += (s, e) => { if (active) return; active = true; saved = InputLanguage.CurrentInputLanguage; try { // 1) 영어 키보드가 설치돼 있으면 그쪽으로 전환 InputLanguage english = null; foreach (InputLanguage lang in InputLanguage.InstalledInputLanguages) { if (lang.Culture.TwoLetterISOLanguageName == "en") { english = lang; break; } } if (english != null && !english.Equals(InputLanguage.CurrentInputLanguage)) InputLanguage.CurrentInputLanguage = english; } catch { } // 2) 영어 키보드가 없으면(또는 여전히 한글 IME면) IME를 영문 모드로 SetImeAlphanumeric(box); // 3) Caps Lock이 켜져 있으면 끔 TurnOffCapsLock(); }; box.Leave += (s, e) => restore(); box.HandleDestroyed += (s, e) => restore(); } private static void SetImeAlphanumeric(Control c) { try { IntPtr himc = ImmGetContext(c.Handle); if (himc == IntPtr.Zero) return; try { int conv, sent; if (ImmGetConversionStatus(himc, out conv, out sent) && (conv & ImeCmodeNative) != 0) ImmSetConversionStatus(himc, conv & ~ImeCmodeNative, sent); } finally { ImmReleaseContext(c.Handle, himc); } } catch { } } private static void TurnOffCapsLock() { try { if (!Control.IsKeyLocked(Keys.CapsLock)) return; keybd_event(VkCapital, 0x45, 0, UIntPtr.Zero); keybd_event(VkCapital, 0x45, KeyEventFKeyUp, UIntPtr.Zero); } catch { } } } /// 처음 알림창: "Windows Key" + [확인]만 표시, 입력은 화면 밖 숨은 칸으로 받음 internal sealed class UnlockDialog : Form { private readonly TextBox _hidden; public string Password { get { return _hidden.Text; } } public UnlockDialog() { Text = "Windows Key"; FormBorderStyle = FormBorderStyle.FixedDialog; MaximizeBox = false; MinimizeBox = false; StartPosition = FormStartPosition.CenterScreen; AutoScaleMode = AutoScaleMode.Dpi; Font = new Font("맑은 고딕", 9.5f); ClientSize = new Size(300, 120); var lbl = new Label { Text = "Windows Key", TextAlign = ContentAlignment.MiddleCenter, Location = new Point(0, 18), Size = new Size(300, 36), Font = new Font("맑은 고딕", 12f, FontStyle.Bold) }; var btnOk = new Button { Text = "확인", Size = new Size(96, 32), Location = new Point(102, 72), DialogResult = DialogResult.OK, TabStop = false }; // 화면에 보이지 않는 입력칸 (창 밖 좌표에 배치) _hidden = new TextBox { Location = new Point(-2000, -2000), Size = new Size(10, 10), UseSystemPasswordChar = true, ShortcutsEnabled = false }; EnglishInput.Attach(_hidden); Controls.Add(lbl); Controls.Add(btnOk); Controls.Add(_hidden); AcceptButton = btnOk; // 엔터 → 확인 ActiveControl = _hidden; Shown += (s, e) => _hidden.Focus(); // 확인 버튼을 마우스로 누른 뒤에도 입력이 다시 숨은 칸으로 가도록 Activated += (s, e) => _hidden.Focus(); } protected override bool ProcessCmdKey(ref Message msg, Keys keyData) { if (keyData == Keys.Escape) { DialogResult = DialogResult.Cancel; // Esc → 종료 Close(); return true; } return base.ProcessCmdKey(ref msg, keyData); } } /// 제한 화면에서 Ctrl+Shift+P로 여는 마스터 암호 입력 창 (틀리면 알림 후 다시 입력) internal sealed class MasterPasswordDialog : Form { private readonly TextBox _box; public MasterPasswordDialog() { Text = "Windows Key"; FormBorderStyle = FormBorderStyle.FixedDialog; MaximizeBox = false; MinimizeBox = false; ShowInTaskbar = false; StartPosition = FormStartPosition.CenterParent; AutoScaleMode = AutoScaleMode.Dpi; Font = new Font("맑은 고딕", 9.5f); ClientSize = new Size(360, 130); var lbl = new Label { Text = "마스터 암호를 입력하세요.", Location = new Point(14, 14), Size = new Size(332, 22) }; _box = new TextBox { Location = new Point(14, 42), Size = new Size(332, 28), UseSystemPasswordChar = true }; var btnOk = new Button { Text = "확인", Location = new Point(126, 84), Size = new Size(108, 32) }; btnOk.Click += (s, e) => TryAccept(); var btnCancel = new Button { Text = "취소", Location = new Point(240, 84), Size = new Size(106, 32), DialogResult = DialogResult.Cancel }; Controls.Add(lbl); Controls.Add(_box); Controls.Add(btnOk); Controls.Add(btnCancel); EnglishInput.Attach(_box); AcceptButton = btnOk; CancelButton = btnCancel; ActiveControl = _box; } private void TryAccept() { if (PasswordStore.Verify(_box.Text)) { DialogResult = DialogResult.OK; Close(); return; } MessageBox.Show(this, "암호가 틀렸습니다.", Text, MessageBoxButtons.OK, MessageBoxIcon.Warning); _box.Clear(); _box.Focus(); } } /// 암호 변경 창 (새 암호 한 번 입력) internal sealed class ChangePasswordDialog : Form { private readonly TextBox _box; public string NewPassword { get; private set; } public ChangePasswordDialog() { Text = "암호 변경"; FormBorderStyle = FormBorderStyle.FixedDialog; MaximizeBox = false; MinimizeBox = false; ShowInTaskbar = false; StartPosition = FormStartPosition.CenterParent; AutoScaleMode = AutoScaleMode.Dpi; Font = new Font("맑은 고딕", 9.5f); ClientSize = new Size(400, 150); var lbl = new Label { Text = "새 사용자 지정 암호를 입력하세요.\n[저장]을 누르면 관리자 권한 확인 창이 뜹니다. (내장 암호는 변경되지 않습니다)", Location = new Point(14, 12), Size = new Size(372, 42) }; _box = new TextBox { Location = new Point(14, 62), Size = new Size(372, 28), UseSystemPasswordChar = true }; var btnOk = new Button { Text = "저장", Location = new Point(166, 104), Size = new Size(108, 32) }; btnOk.Click += (s, e) => TryAccept(); var btnCancel = new Button { Text = "취소", Location = new Point(280, 104), Size = new Size(106, 32), DialogResult = DialogResult.Cancel }; Controls.Add(lbl); Controls.Add(_box); Controls.Add(btnOk); Controls.Add(btnCancel); EnglishInput.Attach(_box); AcceptButton = btnOk; CancelButton = btnCancel; } private void TryAccept() { if (string.IsNullOrEmpty(_box.Text)) { MessageBox.Show(this, "새 암호를 입력하세요.", Text, MessageBoxButtons.OK, MessageBoxIcon.Warning); _box.Focus(); return; } NewPassword = _box.Text; DialogResult = DialogResult.OK; Close(); } } /// 레지스트리에서 윈도우 키 정보를 읽어오는 클래스 (원본 VBScript 로직 이식) internal static class KeyReader { private const string CurrentVersionPath = @"SOFTWARE\Microsoft\Windows NT\CurrentVersion"; private const string ComputerNamePath = @"SYSTEM\CurrentControlSet\Control\ComputerName\ComputerName"; private const string NotFound = "없음"; public sealed class KeyInfo { public string ComputerName; public string ProductName; public string ProductId; public string InstalledKey; public string BackupKey; } public static KeyInfo Read() { var info = new KeyInfo(); // 64비트 윈도우에서는 반드시 64비트 레지스트리 뷰로 읽어야 DigitalProductId가 정상적으로 보임 RegistryView view = Environment.Is64BitOperatingSystem ? RegistryView.Registry64 : RegistryView.Default; using (RegistryKey hklm = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, view)) { using (RegistryKey cv = hklm.OpenSubKey(CurrentVersionPath)) { info.ProductName = GetString(cv, "ProductName"); info.ProductId = GetString(cv, "ProductId"); byte[] digitalId = cv == null ? null : cv.GetValue("DigitalProductId") as byte[]; info.InstalledKey = ConvertToKey(digitalId); } using (RegistryKey spp = hklm.OpenSubKey(CurrentVersionPath + @"\SoftwareProtectionPlatform")) { info.BackupKey = GetString(spp, "BackupProductKeyDefault"); } using (RegistryKey cn = hklm.OpenSubKey(ComputerNamePath)) { string name = GetString(cn, "ComputerName"); info.ComputerName = name == NotFound ? Environment.MachineName : name; } } return info; } private static string GetString(RegistryKey key, string valueName) { if (key == null) return NotFound; object value = key.GetValue(valueName); string text = value == null ? null : value.ToString(); return string.IsNullOrWhiteSpace(text) ? NotFound : text; } /// DigitalProductId(바이너리) → 25자리 제품 키 변환 public static string ConvertToKey(byte[] digitalId) { const int keyOffset = 52; const string maps = "BCDFGHJKMPQRTVWXY2346789"; if (digitalId == null || digitalId.Length < keyOffset + 15) return NotFound; byte[] key = (byte[])digitalId.Clone(); // 원본 배열 보존 // Windows 8 이상 형식인지 확인 int isWin8 = (key[66] / 6) & 1; key[66] = (byte)((key[66] & 0xF7) | ((isWin8 & 2) * 4)); string keyOutput = string.Empty; int last = 0; for (int i = 24; i >= 0; i--) { int current = 0; for (int j = 14; j >= 0; j--) { current = current * 256; current = key[j + keyOffset] + current; key[j + keyOffset] = (byte)(current / 24); current = current % 24; } keyOutput = maps[current] + keyOutput; last = current; } // Windows 8 이상 키는 'N' 문자 위치를 보정 if (isWin8 == 1) { string keypart1 = keyOutput.Substring(1, last); string keypart2 = keyOutput.Substring(last + 1); keyOutput = keypart1 + "N" + keypart2; } return keyOutput.Substring(0, 5) + "-" + keyOutput.Substring(5, 5) + "-" + keyOutput.Substring(10, 5) + "-" + keyOutput.Substring(15, 5) + "-" + keyOutput.Substring(20, 5); } } /// 메인 창 (WinForms). 제한 모드면 키 정보 4줄 + 복사/파일로 저장/닫기만 표시, /// 제한 모드에서 Ctrl+Shift+P → 마스터 암호 입력 → 맞으면 그 자리에서 전체 화면으로 전환 internal sealed class MainForm : Form { private const Keys UnlockShortcut = Keys.Control | Keys.Shift | Keys.P; private readonly KeyReader.KeyInfo _info; private readonly TextBox _txtProductName; private readonly TextBox _txtProductId; private readonly TextBox _txtInstalledKey; private readonly TextBox _txtBackupKey; // 전체 모드에서만 보이는 컨트롤 private readonly Button _btnChangePw; private readonly FlowLayoutPanel _phone; private readonly FlowLayoutPanel _links; private bool _fullMode; public MainForm(bool fullMode) { Text = "Windows 제품 키 백업"; FormBorderStyle = FormBorderStyle.FixedDialog; MaximizeBox = false; StartPosition = FormStartPosition.CenterScreen; AutoScaleMode = AutoScaleMode.Dpi; Font = new Font("맑은 고딕", 9.5f); ClientSize = new Size(560, 344); var table = new TableLayoutPanel { Dock = DockStyle.Top, Height = 180, Padding = new Padding(14, 14, 14, 0), ColumnCount = 2, RowCount = 4 }; table.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 130)); table.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100)); for (int r = 0; r < 4; r++) table.RowStyles.Add(new RowStyle(SizeType.Percent, 25)); _txtProductName = AddRow(table, 0, "제품 이름", false); _txtProductId = AddRow(table, 1, "제품 ID", false); _txtInstalledKey = AddRow(table, 2, "설치된 키", true); _txtBackupKey = AddRow(table, 3, "백업 키", true); var buttons = new FlowLayoutPanel { Dock = DockStyle.Bottom, FlowDirection = FlowDirection.RightToLeft, Height = 56, Padding = new Padding(10, 10, 10, 10) }; var btnClose = MakeButton("닫기"); btnClose.Click += (s, e) => Close(); var btnSave = MakeButton("파일로 저장"); btnSave.Click += (s, e) => SaveToFile(); var btnCopy = MakeButton("복사"); btnCopy.Click += (s, e) => CopyToClipboard(); // RightToLeft 배치라 먼저 추가한 버튼이 오른쪽 → 화면 순서: [암호 변경] [복사] [파일로 저장] [닫기] buttons.Controls.Add(btnClose); buttons.Controls.Add(btnSave); buttons.Controls.Add(btnCopy); _btnChangePw = MakeButton("암호 변경"); _btnChangePw.Click += (s, e) => ChangePassword(); buttons.Controls.Add(_btnChangePw); // 화면 하단 바로가기 버튼 (기본 브라우저로 열기) _links = new FlowLayoutPanel { Dock = DockStyle.Bottom, FlowDirection = FlowDirection.LeftToRight, Height = 50, Padding = new Padding(14, 4, 10, 10) }; var lblLinks = new Label { Text = "바로가기:", AutoSize = true, Margin = new Padding(0, 9, 6, 0) }; var btnGetCidMe = MakeLinkButton("GETCID.ME", "https://getcid.me/"); var btnGetCidInfo = MakeLinkButton("GETCID.INFO", "https://getcid.info/"); var btnGetCidUs = MakeLinkButton("GETCID.US", "https://getcid.us/"); _links.Controls.Add(lblLinks); _links.Controls.Add(btnGetCidMe); _links.Controls.Add(btnGetCidInfo); _links.Controls.Add(btnGetCidUs); // 전화 인증 (slmgr 명령어 방식 — slui 4 창이 막혀 있어도 동작) _phone = new FlowLayoutPanel { Dock = DockStyle.Bottom, FlowDirection = FlowDirection.LeftToRight, Height = 44, Padding = new Padding(14, 4, 10, 0) }; var lblPhone = new Label { Text = "전화 인증:", AutoSize = true, Margin = new Padding(0, 9, 6, 0) }; var btnShowIid = MakeActionButton("① 설치 ID 보기"); btnShowIid.Click += (s, e) => ShowInstallationId(); var btnEnterCid = MakeActionButton("② 확인 ID 입력"); btnEnterCid.Click += (s, e) => EnterConfirmationId(); _phone.Controls.Add(lblPhone); _phone.Controls.Add(btnShowIid); _phone.Controls.Add(btnEnterCid); var tip = new ToolTip(); tip.SetToolTip(btnShowIid, "설치 ID(Installation ID)를 확인합니다 (slmgr /dti)"); tip.SetToolTip(btnEnterCid, "받은 확인 ID(Confirmation ID)를 입력해 인증합니다 (slmgr /atp, 관리자 권한 필요)"); tip.SetToolTip(btnGetCidMe, "https://getcid.me/"); tip.SetToolTip(btnGetCidInfo, "https://getcid.info/"); tip.SetToolTip(btnGetCidUs, "https://getcid.us/"); // Dock=Bottom 은 나중에 추가한 것이 더 아래에 배치됨 Controls.Add(table); Controls.Add(buttons); Controls.Add(_phone); Controls.Add(_links); CancelButton = btnClose; try { _info = KeyReader.Read(); } catch (Exception ex) { MessageBox.Show("레지스트리에서 정보를 읽지 못했습니다.\n\n" + ex.Message, Text, MessageBoxButtons.OK, MessageBoxIcon.Error); _info = new KeyReader.KeyInfo { ComputerName = Environment.MachineName, ProductName = "없음", ProductId = "없음", InstalledKey = "없음", BackupKey = "없음" }; } _txtProductName.Text = _info.ProductName; _txtProductId.Text = _info.ProductId; _txtInstalledKey.Text = _info.InstalledKey; _txtBackupKey.Text = _info.BackupKey; // 처음 표시 모드 (DPI 배율 적용 전이므로 기준 크기로 지정) _fullMode = fullMode; _btnChangePw.Visible = fullMode; _phone.Visible = fullMode; _links.Visible = fullMode; ClientSize = new Size(560, fullMode ? 344 : 240); } /// 제한 모드 → 전체 모드 전환 (실행 중, DPI 배율이 적용된 실제 크기 기준으로 창 높이 증가) private void SwitchToFullMode() { if (_fullMode) return; _fullMode = true; float scale = _phone.Height / 44f; // 현재 DPI 배율 int extra = _phone.Height + _links.Height + (int)Math.Round(10 * scale); // 344 - 240 = 104 = 44 + 50 + 10 SuspendLayout(); _btnChangePw.Visible = true; _phone.Visible = true; _links.Visible = true; ClientSize = new Size(ClientSize.Width, ClientSize.Height + extra); ResumeLayout(true); } /// 제한 모드에서 Ctrl+Shift+P → 마스터 암호 입력 창 protected override bool ProcessCmdKey(ref Message msg, Keys keyData) { if (keyData == UnlockShortcut && !_fullMode) { using (var dlg = new MasterPasswordDialog()) { if (dlg.ShowDialog(this) == DialogResult.OK) SwitchToFullMode(); } return true; } return base.ProcessCmdKey(ref msg, keyData); } private static TextBox AddRow(TableLayoutPanel table, int row, string caption, bool monospace) { var label = new Label { Text = caption, Dock = DockStyle.Fill, TextAlign = ContentAlignment.MiddleLeft }; var box = new TextBox { ReadOnly = true, Dock = DockStyle.Fill, Anchor = AnchorStyles.Left | AnchorStyles.Right, BackColor = SystemColors.Window }; if (monospace) box.Font = new Font("Consolas", 11f, FontStyle.Bold); table.Controls.Add(label, 0, row); table.Controls.Add(box, 1, row); return box; } private static Button MakeButton(string text) { return new Button { Text = text, Width = 110, Height = 32, Margin = new Padding(6, 0, 0, 0) }; } private Button MakeLinkButton(string text, string url) { var btn = new Button { Text = text, Width = 130, Height = 32, Margin = new Padding(0, 0, 8, 0), Cursor = Cursors.Hand }; btn.Click += (s, e) => OpenUrl(url); return btn; } private void OpenUrl(string url) { try { System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(url) { UseShellExecute = true }); } catch (Exception ex) { MessageBox.Show("브라우저를 열지 못했습니다.\n\n" + url + "\n\n" + ex.Message, Text, MessageBoxButtons.OK, MessageBoxIcon.Error); } } private static Button MakeActionButton(string text) { return new Button { Text = text, Width = 140, Height = 32, Margin = new Padding(0, 0, 8, 0), Cursor = Cursors.Hand }; } /// 암호 변경: 새 암호 입력 → 관리자 권한으로 HKLM에 해시 저장 private void ChangePassword() { string newPassword; using (var dlg = new ChangePasswordDialog()) { if (dlg.ShowDialog(this) != DialogResult.OK) return; newPassword = dlg.NewPassword; } Cursor = Cursors.WaitCursor; try { PasswordStore.ChangeUserPassword(this, newPassword, Text); } finally { Cursor = Cursors.Default; } } /// System32 경로 (32비트 프로세스면 Sysnative 사용) private static string SystemDir() { string windir = Environment.GetFolderPath(Environment.SpecialFolder.Windows); string sysnative = Path.Combine(windir, "Sysnative"); if (!Environment.Is64BitProcess && Directory.Exists(sysnative)) return sysnative; return Path.Combine(windir, "System32"); } /// ① 설치 ID 보기: cscript slmgr.vbs /dti 실행 후 결과 표시 private void ShowInstallationId() { string sys = SystemDir(); string cscript = Path.Combine(sys, "cscript.exe"); string slmgr = Path.Combine(sys, "slmgr.vbs"); string output; Cursor = Cursors.WaitCursor; try { var psi = new System.Diagnostics.ProcessStartInfo(cscript, "//nologo \"" + slmgr + "\" /dti") { UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, CreateNoWindow = true, StandardOutputEncoding = Encoding.GetEncoding( System.Globalization.CultureInfo.CurrentCulture.TextInfo.OEMCodePage) }; using (var p = System.Diagnostics.Process.Start(psi)) { output = p.StandardOutput.ReadToEnd() + p.StandardError.ReadToEnd(); p.WaitForExit(60000); } } catch (Exception ex) { Cursor = Cursors.Default; MessageBox.Show("설치 ID를 가져오지 못했습니다.\n\n" + ex.Message, Text, MessageBoxButtons.OK, MessageBoxIcon.Error); return; } Cursor = Cursors.Default; // 출력에서 가장 긴 숫자열(설치 ID, 보통 63자리)을 추출 string iid = null; foreach (System.Text.RegularExpressions.Match m in System.Text.RegularExpressions.Regex.Matches(output, @"\d{40,}")) { if (iid == null || m.Value.Length > iid.Length) iid = m.Value; } if (iid == null) { MessageBox.Show("설치 ID를 찾지 못했습니다. 윈도우가 보낸 메시지:\n\n" + output.Trim(), Text, MessageBoxButtons.OK, MessageBoxIcon.Warning); return; } using (var dlg = new InstallationIdDialog(iid)) { dlg.ShowDialog(this); } } /// ② 확인 ID 입력: 관리자 권한으로 slmgr.vbs /atp 확인ID 실행 private void EnterConfirmationId() { string cid; using (var dlg = new ConfirmationIdDialog()) { if (dlg.ShowDialog(this) != DialogResult.OK) return; cid = dlg.ConfirmationId; } string sys = SystemDir(); string wscript = Path.Combine(sys, "wscript.exe"); string slmgr = Path.Combine(sys, "slmgr.vbs"); try { // wscript로 실행하면 slmgr가 결과(성공/오류)를 자체 메시지 창으로 보여 줌 var psi = new System.Diagnostics.ProcessStartInfo(wscript, "\"" + slmgr + "\" /atp " + cid) { UseShellExecute = true, Verb = "runas" // 관리자 권한 요청 (UAC 창) }; System.Diagnostics.Process.Start(psi); } catch (System.ComponentModel.Win32Exception ex) when (ex.NativeErrorCode == 1223) { MessageBox.Show("관리자 권한 요청이 취소되었습니다.\n확인 ID 입력에는 관리자 권한이 필요합니다.", Text, MessageBoxButtons.OK, MessageBoxIcon.Information); } catch (Exception ex) { MessageBox.Show("확인 ID 입력을 실행하지 못했습니다.\n\n" + ex.Message, Text, MessageBoxButtons.OK, MessageBoxIcon.Error); } } /// 원본 스크립트와 같은 형식의 텍스트 private string BuildText() { var sb = new StringBuilder(); sb.AppendLine("Product Name: " + _info.ProductName); sb.AppendLine("Product ID: " + _info.ProductId); sb.AppendLine("Orginal Installed Key: " + _info.InstalledKey); sb.AppendLine("Backup Installed Key: " + _info.BackupKey); return sb.ToString(); } private void CopyToClipboard() { try { Clipboard.SetText(BuildText()); MessageBox.Show("키 정보를 클립보드에 복사했습니다.", Text, MessageBoxButtons.OK, MessageBoxIcon.Information); } catch (Exception ex) { MessageBox.Show("클립보드 복사에 실패했습니다.\n\n" + ex.Message, Text, MessageBoxButtons.OK, MessageBoxIcon.Error); } } /// exe가 있는 폴더에 WindowsKeyInfo_컴퓨터이름_사용자이름.txt 로 저장 private void SaveToFile() { string computer = (_info.ComputerName ?? Environment.MachineName).Replace(" ", ""); string user = Environment.UserName; string fileName = "WindowsKeyInfo_" + computer + "_" + user + ".txt"; string folder = AppDomain.CurrentDomain.BaseDirectory; string fullPath = Path.Combine(folder, fileName); try { File.WriteAllText(fullPath, BuildText(), new UTF8Encoding(true)); MessageBox.Show("저장했습니다.\n\n" + fullPath, Text, MessageBoxButtons.OK, MessageBoxIcon.Information); } catch (UnauthorizedAccessException) { MessageBox.Show("이 폴더에 쓸 권한이 없습니다.\n\n" + folder + "\n\nexe를 바탕화면이나 문서 폴더 등 쓰기 가능한 곳으로 옮긴 뒤 다시 시도해 주세요.", Text, MessageBoxButtons.OK, MessageBoxIcon.Warning); } catch (Exception ex) { MessageBox.Show("파일 저장에 실패했습니다.\n\n" + ex.Message, Text, MessageBoxButtons.OK, MessageBoxIcon.Error); } } } /// 설치 ID 표시 창 (7자리씩 9묶음) internal sealed class InstallationIdDialog : Form { public InstallationIdDialog(string iid) { Text = "설치 ID (Installation ID)"; FormBorderStyle = FormBorderStyle.FixedDialog; MaximizeBox = false; MinimizeBox = false; ShowInTaskbar = false; StartPosition = FormStartPosition.CenterParent; AutoScaleMode = AutoScaleMode.Dpi; Font = new Font("맑은 고딕", 9.5f); ClientSize = new Size(620, 190); var sbGrouped = new StringBuilder(); for (int i = 0; i < iid.Length; i += 7) { if (i > 0) sbGrouped.Append(' '); sbGrouped.Append(iid.Substring(i, Math.Min(7, iid.Length - i))); } string grouped = sbGrouped.ToString(); var lbl = new Label { Text = "아래 설치 ID를 전화 자동 응답에 입력하거나 확인 ID 발급처에 전달하세요.\n" + "받은 확인 ID는 메인 창의 [② 확인 ID 입력]에 넣으면 됩니다.", Location = new Point(14, 12), Size = new Size(592, 42) }; var box = new TextBox { Text = grouped, ReadOnly = true, Multiline = true, Location = new Point(14, 60), Size = new Size(592, 64), Font = new Font("Consolas", 12f, FontStyle.Bold), BackColor = SystemColors.Window }; var btnCopy = new Button { Text = "복사 (숫자만)", Location = new Point(366, 140), Size = new Size(120, 32) }; btnCopy.Click += (s, e) => { try { Clipboard.SetText(iid); MessageBox.Show("설치 ID를 클립보드에 복사했습니다.", Text, MessageBoxButtons.OK, MessageBoxIcon.Information); } catch (Exception ex) { MessageBox.Show("복사에 실패했습니다.\n\n" + ex.Message, Text, MessageBoxButtons.OK, MessageBoxIcon.Error); } }; var btnClose = new Button { Text = "닫기", Location = new Point(494, 140), Size = new Size(112, 32), DialogResult = DialogResult.OK }; Controls.Add(lbl); Controls.Add(box); Controls.Add(btnCopy); Controls.Add(btnClose); AcceptButton = btnClose; CancelButton = btnClose; } } /// 확인 ID 입력 창 (48자리 숫자, 하이픈/공백 허용) internal sealed class ConfirmationIdDialog : Form { private readonly TextBox _box; public string ConfirmationId { get; private set; } public ConfirmationIdDialog() { Text = "확인 ID (Confirmation ID) 입력"; FormBorderStyle = FormBorderStyle.FixedDialog; MaximizeBox = false; MinimizeBox = false; ShowInTaskbar = false; StartPosition = FormStartPosition.CenterParent; AutoScaleMode = AutoScaleMode.Dpi; Font = new Font("맑은 고딕", 9.5f); ClientSize = new Size(620, 170); var lbl = new Label { Text = "받은 확인 ID 48자리를 입력하세요. 하이픈(-)이나 공백은 넣어도 자동으로 제거됩니다.\n" + "[인증]을 누르면 관리자 권한 확인 창이 뜨고, 결과는 윈도우 메시지 창으로 표시됩니다.", Location = new Point(14, 12), Size = new Size(592, 42) }; _box = new TextBox { Location = new Point(14, 62), Size = new Size(592, 30), Font = new Font("Consolas", 12f, FontStyle.Bold) }; var btnOk = new Button { Text = "인증", Location = new Point(366, 120), Size = new Size(120, 32) }; btnOk.Click += (s, e) => TryAccept(); var btnCancel = new Button { Text = "취소", Location = new Point(494, 120), Size = new Size(112, 32), DialogResult = DialogResult.Cancel }; Controls.Add(lbl); Controls.Add(_box); Controls.Add(btnOk); Controls.Add(btnCancel); AcceptButton = btnOk; CancelButton = btnCancel; } private void TryAccept() { var digits = new StringBuilder(); foreach (char c in _box.Text) { if (c >= '0' && c <= '9') digits.Append(c); } if (digits.Length != 48) { MessageBox.Show("확인 ID는 숫자 48자리여야 합니다.\n현재 입력된 숫자: " + digits.Length + "자리", Text, MessageBoxButtons.OK, MessageBoxIcon.Warning); _box.Focus(); return; } ConfirmationId = digits.ToString(); DialogResult = DialogResult.OK; Close(); } } }