From 91c01ee5b17a0fe3cd8e87b9db937effca74afef Mon Sep 17 00:00:00 2001 From: AndNowWhat Date: Thu, 19 Feb 2026 11:24:55 +0100 Subject: [PATCH] agent remove --- Services/AgentDownloadService.cs | 158 -------------- Services/LoaderAutomationService.cs | 322 ---------------------------- 2 files changed, 480 deletions(-) delete mode 100644 Services/AgentDownloadService.cs delete mode 100644 Services/LoaderAutomationService.cs diff --git a/Services/AgentDownloadService.cs b/Services/AgentDownloadService.cs deleted file mode 100644 index 70f636d..0000000 --- a/Services/AgentDownloadService.cs +++ /dev/null @@ -1,158 +0,0 @@ -using System; -using System.IO; -using System.Net.Http; -using System.Text; -using System.Text.Json; -using System.Threading; -using System.Threading.Tasks; - -namespace Sigil.Services; - -/// -/// Reads the loader's token.bin and downloads the agent from the BWU API -/// so we can inspect what the loader gets (~29.6 MB). Saves to a file for analysis. -/// -public sealed class AgentDownloadService -{ - private const string AgentDownloadUrl = "https://botwithus.com/api/download/agent"; - - private readonly HttpClient _httpClient = new() - { - Timeout = TimeSpan.FromMinutes(2) - }; - - /// - /// Reads the Bearer token from the loader's token.bin. - /// Tries: raw UTF-8 string (trimmed), then JSON with access_token / token / bearer. - /// - /// Full path to token.bin (e.g. loader folder). - /// The token string to use as Authorization: Bearer <token>. - public string ReadTokenFromFile(string tokenBinPath) - { - if (string.IsNullOrWhiteSpace(tokenBinPath) || !File.Exists(tokenBinPath)) - throw new FileNotFoundException("token.bin not found.", tokenBinPath); - - var bytes = File.ReadAllBytes(tokenBinPath); - if (bytes.Length == 0) - throw new InvalidOperationException("token.bin is empty."); - - // Try UTF-8 string first (raw token) - var asUtf8 = Encoding.UTF8.GetString(bytes).Trim(); - if (!string.IsNullOrWhiteSpace(asUtf8)) - { - // If it looks like JSON, parse for token fields - if (asUtf8.StartsWith('{')) - { - try - { - using var doc = JsonDocument.Parse(asUtf8); - var root = doc.RootElement; - foreach (var key in new[] { "access_token", "token", "bearer", "accessToken" }) - { - if (root.TryGetProperty(key, out var prop)) - { - var value = prop.GetString(); - if (!string.IsNullOrWhiteSpace(value)) - return value; - } - } - } - catch - { - // Not valid JSON or no token field; use raw string if it looks like a token - } - } - // Use as raw Bearer token if it's not empty and doesn't look like binary - if (asUtf8.Length < 10_000 && asUtf8.IndexOf('\0') < 0) - return asUtf8; - } - - throw new InvalidOperationException( - "Could not read a token from token.bin. Expected UTF-8 text (raw token or JSON with access_token/token)."); - } - - /// - /// Downloads the agent from the BWU API using the given Bearer token - /// and saves it to for inspection. - /// - /// Token from token.bin (Authorization: Bearer). - /// Where to save the response (e.g. agent_download.bin). - /// Cancellation. - /// Number of bytes written and whether response looks like a PE (MZ header). - public async Task DownloadAgentAsync( - string bearerToken, - string saveFilePath, - CancellationToken cancellationToken = default) - { - if (string.IsNullOrWhiteSpace(bearerToken)) - throw new ArgumentException("Bearer token is required.", nameof(bearerToken)); - - // API returns a presigned S3 URL (body is the URL string). We GET that URL to download the actual DLL. - using var request = new HttpRequestMessage(HttpMethod.Get, AgentDownloadUrl); - request.Headers.TryAddWithoutValidation("Authorization", "Bearer " + bearerToken); - request.Headers.TryAddWithoutValidation("Accept", "*/*"); - - using var response = await _httpClient.SendAsync( - request, - HttpCompletionOption.ResponseHeadersRead, - cancellationToken).ConfigureAwait(false); - - if (!response.IsSuccessStatusCode) - { - var body = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); - throw new InvalidOperationException( - $"Agent download failed: {response.StatusCode}. {body}"); - } - - var downloadUrl = (await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false)).Trim(); - if (string.IsNullOrEmpty(downloadUrl) || !downloadUrl.StartsWith("http", StringComparison.OrdinalIgnoreCase)) - throw new InvalidOperationException("API did not return a download URL."); - - using var downloadRequest = new HttpRequestMessage(HttpMethod.Get, downloadUrl); - using var downloadResponse = await _httpClient.SendAsync( - downloadRequest, - HttpCompletionOption.ResponseHeadersRead, - cancellationToken).ConfigureAwait(false); - downloadResponse.EnsureSuccessStatusCode(); - - await using (var stream = await downloadResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false)) - using (var file = File.Create(saveFilePath)) - { - await stream.CopyToAsync(file, cancellationToken).ConfigureAwait(false); - } - - var length = new FileInfo(saveFilePath).Length; - byte[] header = new byte[2]; - using (var fs = File.OpenRead(saveFilePath)) - _ = await fs.ReadAsync(header.AsMemory(0, 2), cancellationToken).ConfigureAwait(false); - - var isPe = header.Length >= 2 && header[0] == 0x4D && header[1] == 0x5A; // MZ - - return new AgentDownloadResult(saveFilePath, length, isPe); - } - - /// - /// Reads token from token.bin and downloads the agent to a file. - /// - /// Path to loader's token.bin. - /// Where to save (default: temp folder, agent_download_YYYYMMDD_HHmmss.bin). - /// Cancellation. - /// Path, size, and whether the file starts with MZ (PE). - public async Task DownloadAgentToFileAsync( - string tokenBinPath, - string? saveFilePath = null, - CancellationToken cancellationToken = default) - { - var token = ReadTokenFromFile(tokenBinPath); - saveFilePath ??= Path.Combine( - Path.GetTempPath(), - $"agent_download_{DateTime.Now:yyyyMMdd_HHmmss}.dll"); - return await DownloadAgentAsync(token, saveFilePath, cancellationToken).ConfigureAwait(false); - } -} - -/// Result of downloading the agent for inspection. -/// Path where the response was saved. -/// Number of bytes written. -/// True if the file starts with MZ (PE image). -public readonly record struct AgentDownloadResult(string FilePath, long ByteCount, bool StartsWithMz); diff --git a/Services/LoaderAutomationService.cs b/Services/LoaderAutomationService.cs deleted file mode 100644 index c385182..0000000 --- a/Services/LoaderAutomationService.cs +++ /dev/null @@ -1,322 +0,0 @@ -using System; -using System.Diagnostics; -using System.IO; -using System.Runtime.InteropServices; -using System.Threading; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Automation; -using System.Windows.Input; - -namespace Sigil.Services; - -/// -/// Automates the loader UI: find the process table row for the given PID, -/// right-click that row, then select "inject" from the context menu. -/// Loader has columns: pid, data, type, state. Context menu: "inject", "force close". -/// -public sealed class LoaderAutomationService -{ - private const int WaitForLoaderSeconds = 15; - private const int WaitAfterRightClickMs = 400; - private const int WaitForMenuMs = 300; - - /// - /// Starts the loader exe (if path given), waits for its window, finds the row - /// whose pid column matches , right-clicks that row, - /// then selects "inject" from the context menu. - /// - /// Full path to loader.exe. If null/empty, only inject is attempted (loader must already be running). - /// Process ID of the game client – used to find the correct table row. - public async Task LaunchLoaderAndInjectAsync(string? loaderExePath, int gameProcessId) - { - Process? loaderProcess = null; - - if (!string.IsNullOrWhiteSpace(loaderExePath) && File.Exists(loaderExePath)) - { - var startInfo = new ProcessStartInfo - { - FileName = loaderExePath, - UseShellExecute = true - }; - loaderProcess = Process.Start(startInfo); - if (loaderProcess == null) - throw new InvalidOperationException("Failed to start loader."); - } - - IntPtr loaderWindowHandle; - if (loaderProcess != null) - { - loaderWindowHandle = await WaitForMainWindowAsync(loaderProcess, TimeSpan.FromSeconds(WaitForLoaderSeconds)).ConfigureAwait(false); - if (loaderWindowHandle == IntPtr.Zero) - throw new InvalidOperationException("Loader window did not appear in time. Open the loader manually and use right-click → inject."); - await Task.Delay(2000).ConfigureAwait(false); // let loader populate process list - } - else - { - loaderWindowHandle = FindLoaderWindow(); - if (loaderWindowHandle == IntPtr.Zero) - throw new InvalidOperationException("Loader window not found. Start the loader first."); - } - - await Task.Run(() => - { - try - { - RightClickPidRowAndSelectInject(loaderWindowHandle, gameProcessId); - } - catch (Exception ex) - { - throw new InvalidOperationException($"Could not trigger inject in loader UI: {ex.Message}. Use right-click → inject manually.", ex); - } - }).ConfigureAwait(false); - } - - /// - /// Finds the table row for the given PID, right-clicks it, then selects "inject". - /// Tries UI Automation first (table + row by PID); falls back to right-click at window offset + key I. - /// - public void RightClickPidRowAndSelectInject(IntPtr loaderWindowHandle, int gameProcessId) - { - var pidStr = gameProcessId.ToString(); - - if (TryFindRowByPidAndInject(loaderWindowHandle, pidStr)) - return; - - // Fallback: right-click near center-left (where first pid column often is), then press I for "inject" - FallbackRightClickAndInject(loaderWindowHandle); - } - - private static bool TryFindRowByPidAndInject(IntPtr loaderWindowHandle, string pidStr) - { - AutomationElement? root = null; - try - { - root = AutomationElement.FromHandle(loaderWindowHandle); - } - catch - { - return false; - } - - if (root == null) return false; - - // Find table/list (columns: pid, data, type, state) - var tableCondition = new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Table); - var listCondition = new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.List); - var dataGridCondition = new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.DataGrid); - - var tableOrList = root.FindFirst(TreeScope.Descendants, tableCondition) - ?? root.FindFirst(TreeScope.Descendants, dataGridCondition) - ?? root.FindFirst(TreeScope.Descendants, listCondition); - - if (tableOrList == null) return false; - - // Find row (DataItem, TableRow, or ListItem) whose text/name contains our PID - var rowConditions = new[] - { - new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.DataItem), - new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.ListItem) - }; - - AutomationElement? targetRow = null; - foreach (var rowCond in rowConditions) - { - var rows = tableOrList.FindAll(TreeScope.Children, rowCond); - foreach (AutomationElement row in rows) - { - var name = row.Current.Name ?? ""; - // Row might show "34356" or "34356 Unknown Unknown Unmanaged" etc. - if (name.IndexOf(pidStr, StringComparison.Ordinal) >= 0) - { - targetRow = row; - break; - } - } - if (targetRow != null) break; - } - - if (targetRow == null) return false; - - var rect = targetRow.Current.BoundingRectangle; - if (rect.IsEmpty || rect.Width <= 0 || rect.Height <= 0) return false; - - // Right-click center of the pid row - var x = rect.Left + rect.Width / 2; - var y = rect.Top + rect.Height / 2; - RightClickAt(x, y); - Thread.Sleep(WaitAfterRightClickMs); - - // Select "inject" – try UIA menu first, then keyboard I - if (ClickMenuItemContaining("inject")) - return true; - - SendKey(Key.I); - return true; - } - - private static bool ClickMenuItemContaining(string text) - { - var menuCondition = new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Menu); - var root = AutomationElement.RootElement; - var menus = root.FindAll(TreeScope.Children, menuCondition); - foreach (AutomationElement menu in menus) - { - var itemCondition = new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.MenuItem); - var items = menu.FindAll(TreeScope.Descendants, itemCondition); - foreach (AutomationElement item in items) - { - if ((item.Current.Name ?? "").IndexOf(text, StringComparison.OrdinalIgnoreCase) >= 0) - { - var rect = item.Current.BoundingRectangle; - if (!rect.IsEmpty) - { - ClickAt(rect.Left + rect.Width / 2, rect.Top + rect.Height / 2); - return true; - } - } - } - } - return false; - } - - private static void FallbackRightClickAndInject(IntPtr windowHandle) - { - if (!GetWindowRect(windowHandle, out var r)) - return; - // Right-click near left quarter (pid column) and vertical center of content area - var x = r.Left + (r.Right - r.Left) / 4; - var y = r.Top + (r.Bottom - r.Top) / 2; - RightClickAt(x, y); - Thread.Sleep(WaitForMenuMs); - SendKey(Key.I); - } - - private static IntPtr FindLoaderWindow() - { - IntPtr found = IntPtr.Zero; - EnumWindows((hWnd, _) => - { - var length = GetWindowTextLength(hWnd) + 1; - if (length <= 1) return true; - var buf = new char[length]; - if (GetWindowText(hWnd, buf, length) == 0) return true; - var title = new string(buf).TrimEnd('\0'); - // Title like "coaeasy | 9101 Days Left | 1.0.0" or "BWU Loader | ..." - if (title.IndexOf("Loader", StringComparison.OrdinalIgnoreCase) >= 0 || - title.IndexOf("coaeasy", StringComparison.OrdinalIgnoreCase) >= 0 || - title.IndexOf("Days Left", StringComparison.Ordinal) >= 0) - { - found = hWnd; - return false; // stop enum - } - return true; - }, IntPtr.Zero); - return found; - } - - private static async Task WaitForMainWindowAsync(Process process, TimeSpan timeout) - { - var deadline = DateTime.UtcNow + timeout; - while (DateTime.UtcNow < deadline) - { - process.Refresh(); - if (process.MainWindowHandle != IntPtr.Zero) - return process.MainWindowHandle; - await Task.Delay(300).ConfigureAwait(false); - } - return IntPtr.Zero; - } - - private static void RightClickAt(double x, double y) - { - SetCursorPos((int)x, (int)y); - Thread.Sleep(50); - var inputDown = new INPUT { type = InputTypeMouse, mi = new MOUSEINPUT { dwFlags = MouseEventFRightDown } }; - var inputUp = new INPUT { type = InputTypeMouse, mi = new MOUSEINPUT { dwFlags = MouseEventFRightUp } }; - SendInput(1, new[] { inputDown }, Marshal.SizeOf()); - SendInput(1, new[] { inputUp }, Marshal.SizeOf()); - } - - private static void ClickAt(double x, double y) - { - SetCursorPos((int)x, (int)y); - Thread.Sleep(50); - var inputDown = new INPUT { type = InputTypeMouse, mi = new MOUSEINPUT { dwFlags = MouseEventFLeftDown } }; - var inputUp = new INPUT { type = InputTypeMouse, mi = new MOUSEINPUT { dwFlags = MouseEventFLeftUp } }; - SendInput(1, new[] { inputDown }, Marshal.SizeOf()); - SendInput(1, new[] { inputUp }, Marshal.SizeOf()); - } - - private static void SendKey(Key key) - { - var vk = KeyInterop.VirtualKeyFromKey(key); - var inputDown = new INPUT { type = InputTypeKeyboard, ki = new KEYBDINPUT { wVk = (ushort)vk } }; - var inputUp = new INPUT { type = InputTypeKeyboard, ki = new KEYBDINPUT { wVk = (ushort)vk, dwFlags = KeyEventFKeyUp } }; - SendInput(1, new[] { inputDown }, Marshal.SizeOf()); - SendInput(1, new[] { inputUp }, Marshal.SizeOf()); - } - - private const int InputTypeMouse = 0; - private const int InputTypeKeyboard = 1; - private const uint MouseEventFLeftDown = 0x0002; - private const uint MouseEventFLeftUp = 0x0004; - private const uint MouseEventFRightDown = 0x0008; - private const uint MouseEventFRightUp = 0x0010; - private const uint KeyEventFKeyUp = 0x0002; - - private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam); - - [DllImport("user32.dll")] - private static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam); - - [DllImport("user32.dll", CharSet = CharSet.Unicode)] - private static extern int GetWindowText(IntPtr hWnd, char[] lpString, int nMaxCount); - - [DllImport("user32.dll")] - private static extern int GetWindowTextLength(IntPtr hWnd); - - [DllImport("user32.dll")] - private static extern bool SetCursorPos(int x, int y); - - [DllImport("user32.dll")] - private static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize); - - [DllImport("user32.dll")] - private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect); - - [StructLayout(LayoutKind.Sequential)] - private struct RECT - { - public int Left, Top, Right, Bottom; - } - - [StructLayout(LayoutKind.Explicit)] - private struct INPUT - { - [FieldOffset(0)] public int type; - [FieldOffset(8)] public MOUSEINPUT mi; - [FieldOffset(8)] public KEYBDINPUT ki; - } - - [StructLayout(LayoutKind.Sequential)] - private struct MOUSEINPUT - { - public int dx; - public int dy; - public uint mouseData; - public uint dwFlags; - public uint time; - public IntPtr dwExtraInfo; - } - - [StructLayout(LayoutKind.Sequential)] - private struct KEYBDINPUT - { - public ushort wVk; - public ushort wScan; - public uint dwFlags; - public uint time; - public IntPtr dwExtraInfo; - } -}