Here is the code I wrote to open a process:
[DllImport("kernel32.dll", SetLastError = true)]
private static extern UIntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, uint dwProcessId);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool CloseHandle(UIntPtr hObject);
private const uint PROCESS_QUERY_INFORMATION = 0x0400;
public static void processInfo() {
uint PID = 3144;
UIntPtr handle = UIntPtr.Zero;
handle = OpenProcess(PROCESS_QUERY_INFORMATION, false, PID);
Console.WriteLine(Marshal.GetLastWin32Error());
Console.WriteLine(handle);
if (!handle.Equals(UIntPtr.Zero)) {
CloseHandle(handle);
}
}
Marshal.GetLastWin32Error() returns error 1150 for any process. From MSDN:
"ERROR_OLD_WIN_VERSION: The specified program requires a newer version
of Windows."
I'm running this code in Windows 2008 R2 in Visual Studio 2015 Community Edition. Target Framework is set to ".NET Framework 4.5.2" in project settings.
Also, it seems that OpenProcess is still able to do its job because the returned handle is not zero. Should I be concerned about this error?
From the documentation:
If the function succeeds, the return value is an open handle to the
specified process.
If the function fails, the return value is NULL. To get extended error
information, call GetLastError.
Note that the only mention of calling GetLastError is if the function fails. That is indicated by the return value. Only check the error code when the function fails, it only has a meaningful value in that situation. Your mistake is that you check the error code unconditionally.
handle = OpenProcess(...);
if (handle == UIntPtr.Zero)
// only now call Marshal.GetLastWin32Error
Note also that it is pointless to assign handle twice. You wrote:
UIntPtr handle = UIntPtr.Zero;
handle = OpenProcess(...);
Surely the compiler warned that this was pointless, that the value assigned to handle was not used. Your code is somewhat akin to:
int i = 1;
i = 2;
I'm sure you'd never do this. Your code should be:
UIntPtr handle = OpenProcess(...);
I do not know what the problem with your code is, but here is a very simple implementation which I have tested working. Keep in mind you must run as administrator.
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace ConsoleApp3
{
class Program
{
[Flags]
public enum ProcessAccessFlags : uint
{
All = 0x001F0FFF,
Terminate = 0x00000001,
CreateThread = 0x00000002,
VirtualMemoryOperation = 0x00000008,
VirtualMemoryRead = 0x00000010,
VirtualMemoryWrite = 0x00000020,
DuplicateHandle = 0x00000040,
CreateProcess = 0x000000080,
SetQuota = 0x00000100,
SetInformation = 0x00000200,
QueryInformation = 0x00000400,
QueryLimitedInformation = 0x00001000,
Synchronize = 0x00100000
}
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr OpenProcess(
ProcessAccessFlags processAccess, bool bInheritHandle, int processId);
static void Main(string[] args)
{
Process proc = Process.GetProcessesByName("ac_client")[0];
var hProc = OpenProcess(ProcessAccessFlags.All, false, proc.Id);
}
}
}
Related
this error is appearing while I am attempting to do two things.
while attempting this (Code block 1):
_class = new Proc(Process.GetProcessesByName("procname")[0]);
then in the class Proc whats happening is
public Proc(Process _SelectedProcess)
{
Process = _SelectedProcess;
}
public Process Process
{
get
{
return SelectedProcess;
}
set
{
SelectedProcess = value;
if (SelectedProcess != null)
{
Process.EnterDebugMode();
_Reader = new Win32_Memory(value.Handle, value.MainModule.BaseAddress.ToInt32(), value.Id);
}
}
}
That's some of the ways I get the exception, sometimes this passes without any exception for no apparent reason as far as I see.
Note: it never passes in windows 7, I'm using windows 10 and sometimes it happens that the function works
but if it does pass, the next time I need to use OpenProcess() outside of the Process class, I almost always get the exception, and if i do, then afterwards it fails executing code block 1 if I try to do so again.
this (code block 2) also gets the same access denied error, and sometimes doesnt...
if (_Reader.ReadInt(_addr) == 1) _Reader.Write(_addr, 0);
public bool Write(int address, long value)
{
hProc = OpenProcess(ProcessAccessFlags.VMWrite, false, ID);
byte[] val = BitConverter.GetBytes(value);
bool worked = WriteProcessMemory(hProc, new IntPtr(address), val, (uint)val.LongLength, 0);
CloseHandle(hProc);
return worked;
}
the access flags:
[Flags]
public enum ProcessAccessFlags : uint
{
All = 0x001F0FFF,
Terminate = 0x00000001,
CreateThread = 0x00000002,
VMOperation = 0x00000008,
VMRead = 0x00000010,
VMWrite = 0x00000020,
DupHandle = 0x00000040,
SetInformation = 0x00000200,
QueryInformation = 0x00000400,
Synchronize = 0x00100000
}
the imports:
[DllImport("kernel32.dll")]
private static extern IntPtr OpenProcess(ProcessAccessFlags dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, int dwProcessId);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, uint nSize, int unused);
also worth noting that sometimes all of this code gets executed without ANY error and will work for as long as I do not reopen this application or if i do not restart the targeted application.
please help me out on this one, if I wasn't clear on some things - this is my first question and I haven't really ever needed to ask one before this one... so I will explain anything necessary afterwards
If, as your last comment indicates, the processes truly have nothing to do with each other, then that exactly explains the AccessDeniedException. You aren't allowed to just modify memory of any random process. That would be a security hole.
Both processes have to be setup and agree to share memory with each other. There are many ways to do inter-process communication between cooperating processes: here's a start: Shared memory between 2 processes (applications)
How do I monitor current ECX register in C# at given ASM opcode?
Say, I have a:
FF8.exe+69DD8 - push ecx
Using any ready debugger I can do a breakpoint at given opcode and watch registers.
But I need to make an automation software to:
Catch every time 'push ecx' is called and add it to table with the time it was called to know how often is it called.
It's something like cut-scene with different sounds being played, and we need to know what's the time the sounds are played, one after another
Why I can't use ready softwares for this?
Because when I breakpoint 'push ecx', (note: ecx register) and then step over I lose whole timing. I need to do a table with detailed times in which this opcode is accesed.
After four years I am getting back to respond to my own question- Yes, we have to create our own debugger.
It's as simple as calling P/Invoke:
[DllImport("kernel32.dll")]
public static extern bool DebugActiveProcess(int dwProcessId);
Get process id (pId) and call DebugActiveProcess:
Process[] processes = Process.GetProcessesByName(args[0]);
bool bAttached = pinvokes.DebugActiveProcess(processes[0].Id);
Create new struct of DEBUG_EVENT - it would contain all the data about breakpoint
Wait for debug event in a while loop:
WaitForDebugEvent(ref debug, 0xFFFFFFFF);
The breakpoint 0xCC opcode will trigger the EXCEPTION_BREAKPOINT. You now have the threadId associated with given exception. You have to now get the thread context by GetThreadContext- the CONTEXT structure will now contain CPU registers including ECX. You just need to add p/invoke for GetThreadContext and structure for CONTEXT. Full structure and an example can be seen here: http://www.pinvoke.net/default.aspx/kernel32/GetThreadContext.html
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool GetThreadContext(IntPtr hThread, ref CONTEXT lpContext);
The process code will break on every debugging event even on DLL_LOAD. To set a breakpoint at given address you have to actually write an INT 3 opcode. To do that you have to open process in R/W and inject 0xCC to process memory. Use OpenProcess p/invoke and WriteProcessMemory p/invoke:
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr OpenProcess(
ProcessAccessFlags processAccess,
bool bInheritHandle,
int processId
);
public static IntPtr OpenProcess(Process proc, ProcessAccessFlags flags)
{
return OpenProcess(flags, false, proc.Id);
}
[Flags]
public enum ProcessAccessFlags : uint
{
All = 0x001F0FFF,
Terminate = 0x00000001,
CreateThread = 0x00000002,
VirtualMemoryOperation = 0x00000008,
VirtualMemoryRead = 0x00000010,
VirtualMemoryWrite = 0x00000020,
DuplicateHandle = 0x00000040,
CreateProcess = 0x000000080,
SetQuota = 0x00000100,
SetInformation = 0x00000200,
QueryInformation = 0x00000400,
QueryLimitedInformation = 0x00001000,
Synchronize = 0x00100000
}
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool WriteProcessMemory(
IntPtr hProcess,
IntPtr lpBaseAddress,
byte[] lpBuffer,
Int32 nSize,
out IntPtr lpNumberOfBytesWritten);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool WriteProcessMemory(
IntPtr hProcess,
IntPtr lpBaseAddress,
[MarshalAs(UnmanagedType.AsAny)] object lpBuffer,
int dwSize,
out IntPtr lpNumberOfBytesWritten);
Now we have everything required to open, write opcode and break when any thread arrive on that position:
IntPtr procHwnd = pinvokes.OpenProcess(processes[0], pinvokes.ProcessAccessFlags.All);
WriteProcessMemory(procHwnd, new IntPtr(0x469DD8), new byte[] { 0xCC }, 1, out IntPtr lpnum);
the lpAddress should be absolute virtual address- that means that in the question there's FF8.exe+69DD8, so we need to get IMAGE_BASE for selected module which in this case is "FF8.exe" and then add 0x69DD8 to that IMAGE_BASE. Naturally every 32-bit non ASLR processes have IMAGE_BASE set at 0x400000. For ASLR activated processes you have to get process modules and get the BaseAddress like this:
foreach(var mod in processes[0].Modules)
{
if ((mod as ProcessModule).ModuleName == args[3])
{
image_base = (mod as ProcessModule).BaseAddress;
break;
}
}
where args[3] == "FF8.exe"- this should return 0x400000 for non-ASLR processes on 32bit
I have a WinForms project, and if the user want's a debug console, I allocate a console with AllocConsole().
All console output works normally with the target architecture set to "Any CPU", but when I change it to "x86" it doesn't output anything (Console.Read() still works as expected). If I open the EXE directly, the output works. It looks like Visual Studio redirects it into it's own "Output" window.
I also tried this answer, but it didn't work, I also tried Console.SetOut(GetStdHandle(-11)), which didn't work either.
Setting the target architecture to 'Any CPU' is no option for me.
So here are my two questions:
Why is this only the case when the target architecture is set to x86?
How can I output to my console when running inside of Visual Studio?
When "Enable native code debugging" is enabled, output from consoles crated with AllocConsole is redirected to the debug output window instead.
The reason this only happens in x86 and not AnyCPU is because you can only debug native code in an x86 application.
Note that this behavior only occurs with consoles created with AllocConsole. A console application's output is not redirected.
EDIT: The other reason for the console not outputting text is when you've written to the console before calling AllocConsole.
Regardless of the reason, this code will restore output if it was redirected, and reopen the console in case it's invalid. It uses the magic number 7 which is what the handle of stdout usually equals to.
using System;
using System.IO;
using System.Runtime.InteropServices;
public static class ConsoleHelper
{
public static void CreateConsole()
{
AllocConsole();
// stdout's handle seems to always be equal to 7
IntPtr defaultStdout = new IntPtr(7);
IntPtr currentStdout = GetStdHandle(StdOutputHandle);
if (currentStdout != defaultStdout)
// reset stdout
SetStdHandle(StdOutputHandle, defaultStdout);
// reopen stdout
TextWriter writer = new StreamWriter(Console.OpenStandardOutput())
{ AutoFlush = true };
Console.SetOut(writer);
}
// P/Invoke required:
private const UInt32 StdOutputHandle = 0xFFFFFFF5;
[DllImport("kernel32.dll")]
private static extern IntPtr GetStdHandle(UInt32 nStdHandle);
[DllImport("kernel32.dll")]
private static extern void SetStdHandle(UInt32 nStdHandle, IntPtr handle);
[DllImport("kernel32")]
static extern bool AllocConsole();
}
See How to detect if Console.In (stdin) has been redirected? for another way to detect if the console handles have been redirected.
None of the earlier answers worked well for me with VS2017 and Windows 10 (for instance they failed if launch app in debug mode).
Below you can find a little bit enhanced code. Idea is the same, but magic numbers are removed (Ceztko already mentioned that) and all necessary in\out streams are initialized.
This code works for me if create a new console (alwaysCreateNewConsole = true).
Attaching to console of parent process (alwaysCreateNewConsole = false) has several drawbacks. For example I was unable to completely mimic behavior of console app launched from cmd. And I'm not sure that it is possible at all.
And most important: after revision of Console class I reconsidered general idea of using Console class with manually created console. It works well (I hope) for most of the cases, but can bring a lot of pain in future.
static class WinConsole
{
static public void Initialize(bool alwaysCreateNewConsole = true)
{
bool consoleAttached = true;
if (alwaysCreateNewConsole
|| (AttachConsole(ATTACH_PARRENT) == 0
&& Marshal.GetLastWin32Error() != ERROR_ACCESS_DENIED))
{
consoleAttached = AllocConsole() != 0;
}
if (consoleAttached)
{
InitializeOutStream();
InitializeInStream();
}
}
private static void InitializeOutStream()
{
var fs = CreateFileStream("CONOUT$", GENERIC_WRITE, FILE_SHARE_WRITE, FileAccess.Write);
if (fs != null)
{
var writer = new StreamWriter(fs) { AutoFlush = true };
Console.SetOut(writer);
Console.SetError(writer);
}
}
private static void InitializeInStream()
{
var fs = CreateFileStream("CONIN$", GENERIC_READ, FILE_SHARE_READ, FileAccess.Read);
if (fs != null)
{
Console.SetIn(new StreamReader(fs));
}
}
private static FileStream CreateFileStream(string name, uint win32DesiredAccess, uint win32ShareMode,
FileAccess dotNetFileAccess)
{
var file = new SafeFileHandle(CreateFileW(name, win32DesiredAccess, win32ShareMode, IntPtr.Zero, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, IntPtr.Zero), true);
if (!file.IsInvalid)
{
var fs = new FileStream(file, dotNetFileAccess);
return fs;
}
return null;
}
#region Win API Functions and Constants
[DllImport("kernel32.dll",
EntryPoint = "AllocConsole",
SetLastError = true,
CharSet = CharSet.Auto,
CallingConvention = CallingConvention.StdCall)]
private static extern int AllocConsole();
[DllImport("kernel32.dll",
EntryPoint = "AttachConsole",
SetLastError = true,
CharSet = CharSet.Auto,
CallingConvention = CallingConvention.StdCall)]
private static extern UInt32 AttachConsole(UInt32 dwProcessId);
[DllImport("kernel32.dll",
EntryPoint = "CreateFileW",
SetLastError = true,
CharSet = CharSet.Auto,
CallingConvention = CallingConvention.StdCall)]
private static extern IntPtr CreateFileW(
string lpFileName,
UInt32 dwDesiredAccess,
UInt32 dwShareMode,
IntPtr lpSecurityAttributes,
UInt32 dwCreationDisposition,
UInt32 dwFlagsAndAttributes,
IntPtr hTemplateFile
);
private const UInt32 GENERIC_WRITE = 0x40000000;
private const UInt32 GENERIC_READ = 0x80000000;
private const UInt32 FILE_SHARE_READ = 0x00000001;
private const UInt32 FILE_SHARE_WRITE = 0x00000002;
private const UInt32 OPEN_EXISTING = 0x00000003;
private const UInt32 FILE_ATTRIBUTE_NORMAL = 0x80;
private const UInt32 ERROR_ACCESS_DENIED = 5;
private const UInt32 ATTACH_PARRENT = 0xFFFFFFFF;
#endregion
}
Following worked for me in vs 2015, none worked from other answers:
Source: https://social.msdn.microsoft.com/profile/dmitri567/?ws=usercard-mini
using System;
using System.Windows.Forms;
using System.Text;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
namespace WindowsApplication
{
static class Program
{
[DllImport("kernel32.dll",
EntryPoint = "GetStdHandle",
SetLastError = true,
CharSet = CharSet.Auto,
CallingConvention = CallingConvention.StdCall)]
private static extern IntPtr GetStdHandle(int nStdHandle);
[DllImport("kernel32.dll",
EntryPoint = "AllocConsole",
SetLastError = true,
CharSet = CharSet.Auto,
CallingConvention = CallingConvention.StdCall)]
private static extern int AllocConsole();
private const int STD_OUTPUT_HANDLE = -11;
private const int MY_CODE_PAGE = 437;
static void Main(string[] args)
{
Console.WriteLine("This text you can see in debug output window.");
AllocConsole();
IntPtr stdHandle=GetStdHandle(STD_OUTPUT_HANDLE);
SafeFileHandle safeFileHandle = new SafeFileHandle(stdHandle, true);
FileStream fileStream = new FileStream(safeFileHandle, FileAccess.Write);
Encoding encoding = System.Text.Encoding.GetEncoding(MY_CODE_PAGE);
StreamWriter standardOutput = new StreamWriter(fileStream, encoding);
standardOutput.AutoFlush = true;
Console.SetOut(standardOutput);
Console.WriteLine("This text you can see in console window.");
MessageBox.Show("Now I'm happy!");
}
}
}
I also had this problem. Every time I tried to debug my app, the console was blank. Strangely, launching the exe without the debugger worked fine.
I found that I had to Enable the Visual Studio hosting process from the project's Debug menu.
Stephen is correct that Enable native code debugging does redirect the console to the Output window. However, regardless of the native code debugging setting, I saw absolutely no output in either place until I enabled the Visual Studio hosting process.
This could have been the reason that merely disabling native code debugging did not solve your issue.
Just wanted to post the answer from Visual studio Developer community.
https://developercommunity.visualstudio.com/content/problem/12166/console-output-is-gone-in-vs2017-works-fine-when-d.html
Go to this link and look at the answer from Ramkumar Ramesh.
I have tested this code in VS 2017.
I spent one day to find this answer. Hope it helps you as well.
Edit--
As suggessted by Mike to include some description. I would like to suggesst some corrections in Zuniar answer. He tested with VS 2015. But that would not work in VS 2017.
Instead of GetStdHandle, Please use CreateFile reference from kernel32.dll
IntPtr stdHandle = CreateFile("CONOUT$", GENERIC_WRITE, FILE_SHARE_WRITE, 0,
OPEN_EXISTING, 0, 0);
Before adding above code, please declare
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr CreateFile(string lpFileName, uint
dwDesiredAccess, uint dwShareMode, uint lpSecurityAttributes, uint
dwCreationDisposition, uint dwFlagsAndAttributes, uint hTemplateFile);
private const int MY_CODE_PAGE = 437;
private const uint GENERIC_WRITE = 0x40000000;
private const uint FILE_SHARE_WRITE = 0x2;
private const uint OPEN_EXISTING = 0x3;
i have taken this code from the given link.
We have an IIS WCF service that launches another process (app.exe) as a different user. I have complete control over both applications (and this is a dev environment for now). The IIS app pool runs as me, a domain user (DOMAIN\nirvin), who is also a local administrator on the box. The second process is supposed to run as a local user (svc-low). I am using System.Diagnostics.Process.Start(ProcessStartInfo) to launch the process. The process launches successfully - I know because there are no exceptions thrown, and I get a process ID. But the process dies immediately, and I get an error in the Event Log that looks like:
Faulting application name: app.exe, version: 1.0.3.0, time stamp: 0x514cd763
Faulting module name: KERNELBASE.dll, version: 6.2.9200.16451, time stamp: 0x50988aa6
Exception code: 0xc06d007e
Fault offset: 0x000000000003811c
Faulting process id: 0x10a4
Faulting application start time: 0x01ce274b3c83d62d
Faulting application path: C:\Program Files\company\app\app.exe
Faulting module path: C:\Windows\system32\KERNELBASE.dll
Report Id: 7a45cd1c-933e-11e2-93f8-005056b316dd
Faulting package full name:
Faulting package-relative application ID:
I've got pretty thorough logging in app.exe (now), so I don't think it's throwing errors in the .NET code (anymore).
Here's the real obnoxious part: I figured I was just launching the process wrong, so I copied my Process.Start() call in a dumb WinForms app and ran it on the machine as myself, hoping to tinker around till I got the parameters right. So of course that worked the very first time and every time since: I'm able to consistently launch the second process and have it run as intended. It's only launching from IIS that doesn't work.
I've tried giving svc-low permission to "Log on as a batch job" and I've tried giving myself permission to "Replace a process level token" (in Local Security Policy), but neither seem to have made any difference.
Help!
Environment Details
Windows Server 2012
.NET 4.5 (all applications mentioned)
Additional Details
At first app.exe was a Console Application. Trying to launch was making conhost.exe generate errors in the Event Log, so I switched app.exe to be a Windows Application. That took conhost out of the equation but left me the situation described here. (Guided down that path by this question.)
The ProcessStartInfo object I use looks like this:
new ProcessStartInfo
{
FileName = fileName,
Arguments = allArguments,
Domain = domainName,
UserName = userName,
Password = securePassword,
WindowStyle = ProcessWindowStyle.Hidden,
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardOutput = false
//LoadUserProfile = true //I've done it with and without this set
};
An existing question says I should go down to the native API, but a) that question addresses a different situation and b) the success of the dumb WinForms app suggests that Process.Start is a viable choice for the job.
I ended up opening a case with Microsoft, and this is the information I was given:
Process.Start internally calls CreateProcessWithLogonW(CPLW) when credentials are specified. CreateProcessWithLogonW cannot be called from a Windows Service Environment (such as an IIS WCF service). It can only be called from an Interactive Process (an application launched by a user who logged on via CTRL-ALT-DELETE).
(that's verbatim from the support engineer; emphasis mine)
They recommended I use CreateProcessAsUser instead. They gave me some useful sample code, which I then adapted to my needs, and now everything works great!
The end result was this:
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security;
public class ProcessHelper
{
static ProcessHelper()
{
UserToken = IntPtr.Zero;
}
private static IntPtr UserToken { get; set; }
public int StartProcess(ProcessStartInfo processStartInfo)
{
LogInOtherUser(processStartInfo);
Native.STARTUPINFO startUpInfo = new Native.STARTUPINFO();
startUpInfo.cb = Marshal.SizeOf(startUpInfo);
startUpInfo.lpDesktop = string.Empty;
Native.PROCESS_INFORMATION processInfo = new Native.PROCESS_INFORMATION();
bool processStarted = Native.CreateProcessAsUser(UserToken, processStartInfo.FileName, processStartInfo.Arguments,
IntPtr.Zero, IntPtr.Zero, true, 0, IntPtr.Zero, null,
ref startUpInfo, out processInfo);
if (!processStarted)
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
uint processId = processInfo.dwProcessId;
Native.CloseHandle(processInfo.hProcess);
Native.CloseHandle(processInfo.hThread);
return (int) processId;
}
private static void LogInOtherUser(ProcessStartInfo processStartInfo)
{
if (UserToken == IntPtr.Zero)
{
IntPtr tempUserToken = IntPtr.Zero;
string password = SecureStringToString(processStartInfo.Password);
bool loginResult = Native.LogonUser(processStartInfo.UserName, processStartInfo.Domain, password,
Native.LOGON32_LOGON_BATCH, Native.LOGON32_PROVIDER_DEFAULT,
ref tempUserToken);
if (loginResult)
{
UserToken = tempUserToken;
}
else
{
Native.CloseHandle(tempUserToken);
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
}
private static String SecureStringToString(SecureString value)
{
IntPtr stringPointer = Marshal.SecureStringToBSTR(value);
try
{
return Marshal.PtrToStringBSTR(stringPointer);
}
finally
{
Marshal.FreeBSTR(stringPointer);
}
}
public static void ReleaseUserToken()
{
Native.CloseHandle(UserToken);
}
}
internal class Native
{
internal const int LOGON32_LOGON_INTERACTIVE = 2;
internal const int LOGON32_LOGON_BATCH = 4;
internal const int LOGON32_PROVIDER_DEFAULT = 0;
[StructLayout(LayoutKind.Sequential)]
internal struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public uint dwProcessId;
public uint dwThreadId;
}
[StructLayout(LayoutKind.Sequential)]
internal struct STARTUPINFO
{
public int cb;
[MarshalAs(UnmanagedType.LPStr)]
public string lpReserved;
[MarshalAs(UnmanagedType.LPStr)]
public string lpDesktop;
[MarshalAs(UnmanagedType.LPStr)]
public string lpTitle;
public uint dwX;
public uint dwY;
public uint dwXSize;
public uint dwYSize;
public uint dwXCountChars;
public uint dwYCountChars;
public uint dwFillAttribute;
public uint dwFlags;
public short wShowWindow;
public short cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public System.UInt32 nLength;
public IntPtr lpSecurityDescriptor;
public bool bInheritHandle;
}
[DllImport("advapi32.dll", EntryPoint = "LogonUserW", SetLastError = true, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
internal extern static bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
[DllImport("advapi32.dll", EntryPoint = "CreateProcessAsUserA", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
internal extern static bool CreateProcessAsUser(IntPtr hToken, [MarshalAs(UnmanagedType.LPStr)] string lpApplicationName,
[MarshalAs(UnmanagedType.LPStr)] string lpCommandLine, IntPtr lpProcessAttributes,
IntPtr lpThreadAttributes, bool bInheritHandle, uint dwCreationFlags, IntPtr lpEnvironment,
[MarshalAs(UnmanagedType.LPStr)] string lpCurrentDirectory, ref STARTUPINFO lpStartupInfo,
out PROCESS_INFORMATION lpProcessInformation);
[DllImport("kernel32.dll", EntryPoint = "CloseHandle", SetLastError = true, CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
internal extern static bool CloseHandle(IntPtr handle);
}
There are some pre-requisites to making this code work. The user running it must have the user right to 'Replace a process level token' and 'Adjust memory quotas for a process', while the 'other user' must have the user right to 'Log on as a batch job'. These settings can be found under the Local Security Policy (or possibly through Group Policy). If you change them, a restart will be required.
UserToken is a property that can be closed via ReleaseUserToken because we will call StartProcess repeatedly and we were told not to log the other user on again and again.
That SecureStringToString() method was taken from this question. Using SecureString was not part of Microsoft's recommendation; I did it this way so as not to break compatibility with some other code.
Exception code: 0xc06d007e
This is an exception that's specific to Microsoft Visual C++, facility code 0x6d. The error code is 0x007e (126), ERROR_MOD_NOT_FOUND, "The specified module could not be found". This exception is raised when a delay-loaded DLL cannot be found. Most programmers have the code that generates this exception on their machine, vc/include/delayhlp.cpp in the Visual Studio install directory.
Well, it is the typical "file not found" mishap, specific to a DLL. If you have no idea what DLL is missing then you can use SysInternals' ProcMon utility. You'll see the program search for the DLL and not finding just before it bombs.
A classic way to get poorly designed programs to crash with Process.Start() is by not setting the ProcessStartInfo.WorkingDirectory property to the directory in which the EXE is stored. It usually is by accident but won't be when you use the Process class. Doesn't look like you do so tackle that first.
I am trying to invoke SymSrvStoreFileW from DBGHELP.DLL (after a previous failed attempt at getting SymSrvGetFileIndexInfoW to work). The important functions are defined as follows in MSDN:
BOOL WINAPI SymInitialize(
_In_ HANDLE hProcess,
_In_opt_ PCTSTR UserSearchPath, // null is documented as fine
_In_ BOOL fInvadeProcess // false
);
PCTSTR WINAPI SymSrvStoreFile(
_In_ HANDLE hProcess,
_In_opt_ PCTSTR SrvPath, // e.g. "srv*C:\symbols"
_In_ PCTSTR File, // e.g. "C:\myapp.pdb"
_In_ DWORD Flags // I am using SYMSTOREOPT_RETURNINDEX (0x04)
);
BOOL WINAPI SymCleanup(
_In_ HANDLE hProcess
);
hProcess is a little strange, from the documentation I gather that it really doesn't matter what you pass into it, so long as you remain consistent. I have used both the current process ID and a 'any old value' ID. Neither worked.
I made the following externs:
[DllImport("dbghelp.dll", EntryPoint = "SymInitializeW", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
public static extern bool SymInitialize(
IntPtr process,
[param: MarshalAs(UnmanagedType.LPTStr)]
string searchPath,
bool invadeProcess);
[DllImport("dbghelp.dll", EntryPoint = "SymCleanup", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
public static extern bool SymCleanup(IntPtr process);
[DllImport("dbghelp.dll", EntryPoint = "SymSrvStoreFileW", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
[return: MarshalAs(UnmanagedType.LPTStr)]
public static extern string SymSrvStoreFile(
IntPtr process,
[param: MarshalAs(UnmanagedType.LPTStr)]
string srvPath,
[param: MarshalAs(UnmanagedType.LPTStr)]
string file,
SymStoreOpt options);
enum SymStoreOpt : uint
{
None = 0x00,
Compress = 0x01,
Overwrite = 0x02,
Pointer = 0x08,
ReturnIndex = 0x04,
PassIfExists = 0x40,
}
And attempted a call:
var processId = IntPtr.Zero;
try
{
// This succeeds.
processId = new IntPtr(Process.GetCurrentProcess().Id);
if (!NativeMethods.SymInitialize(processId, null, false))
{
processId = IntPtr.Zero;
throw new Win32Exception(Marshal.GetLastWin32Error());
}
// This fails.
var storageLocation = NativeMethods.SymSrvStoreFile(processId, "srv*C:\\vssym", "C:\\test\\mscorlib.pdb", SymStoreOpt.ReturnIndex);
if (storageLocation == null)
{
// Errors under various circumstances.
// - The operation completed successfully (but storageLocation is still null)
// - The specified module could not be found
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
finally
{
if (processId != IntPtr.Zero)
NativeMethods.SymCleanup(processId);
}
Are there any mistakes with my extern declarations? Alternatively, anyone know how to get the symbol storage location for a PDB/PE (and not only a .Net one)? I have also tried the other SymStoreOpt flags and got nowhere.
At some point (I can't remember what I did) the file appeared in the symbol storage location (even though ReturnIndex was used), but at that point my process would crash (one of those where the debugger doesn't even catch it), running it again would result in "the operation succeeded" with no return value.
EDIT: I tried this in C++ and still get the same behavior - obviously I don't want to go that route because P/Invoke has the advantage of architecture neutrality.