Jump to content
robertjohns

Cecking Application Execution

Recommended Posts

Is there any possibility to check if the exe is executed by clicking on its own icon or by external exe

 

Suppose

 

Exe-1 and Exe-2

 

Need to check Exe-1 is whether executed by clicking on its own icon or executed by external Exe-2

Share this post


Link to post

With the Windows API you can find information about your own process from its process ID, and in this "process information" is the process ID of the parent process that started your process. From this you can find whether the name of that process is Explorer or EXE1 (or something else).  If you are not familiar with all this the API calls can appear quite complicated - look for examples in Delphi of using the Windows ToolHelp API, which includes the functions you need, and for which Delphi provides an interface in Winapi.TlHelp32.  There are plenty of code examples around which you can copy but someone else may be able to give you a link to code that does exactly what you need.

Share this post


Link to post
53 minutes ago, Der schöne Günther said:

Do you have control over Exe-2 or is it by somebody else? If it is, can you be sure that the behaviour of Exe-2 might be changing in the future?

No I don't have control over Exe-2, Exe-1 must not run if it is not executed by its own

Edited by robertjohns

Share this post


Link to post
37 minutes ago, timfrost said:

With the Windows API you can find information about your own process from its process ID, and in this "process information" is the process ID of the parent process that started your process. From this you can find whether the name of that process is Explorer or EXE1 (or something else).

It is also possible to check if your process is being executed by a .lnk shortcut, by using the GetStartupInfo() API and checking the STARTUPINFO.dwFlags field for the STARTF_TITLEISLINKNAME flag.

Share this post


Link to post

@robertjohns

 

you would can some like this... 

  • You could check the "processID" of the "parent" that ran the current process. Normally, when running an application, "MSExplorer" is the "parent" (but not necessarily).
  • So, if Exe2 executed Exe1, "Exe2" would have to be the "parent" of "Exe1", naturally, this might not be true at all, as there are ways to bypass this task.

here is a very easy code to find on the internet, and that I just did some checks to try to find the answer

  • im PROJECT1.exe ...
  • see the "2nd" Project1.exe on left Memo and the right Memo = ProcessID from 1º and  ParentID from 2º
    • we'll have 2 instance of "Project1.exe" = 1 with BDS.exe and 1 with "Project1.exe" (from "run me" button)
  • now, just do the changes and clean the code not necessary... the code is not mine! ok

implementation

{$R *.dfm}

uses
  Winapi.ShellAPI,
  Winapi.PsAPI,
  Winapi.TlHelp32;

function SearchParentProcessFrom(const PID: cardinal): string;
var
  hProcess: THandle;
  path    : array [0 .. MAX_PATH - 1] of char;
begin
  result   := '... Parent empty ...';
  hProcess := OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ, false, PID);
  //
  if hProcess <> 0 then
    try
      if (GetModuleFileNameEx(hProcess, 0, path, MAX_PATH) <> 0) then // RaiseLastOSError;
        result := PID.ToString + '=' + ExtractFileName(path);         // get Exename/Module
    finally
      CloseHandle(hProcess)
    end
  else
    result := '.... ERROR hProcess = 0 = No-Opended file = no problem at all';
end;

function CheckProcessOnMemory: TArray<string>;
const
  PROCESS_TERMINATE = $0001;
var
  ContinueLoop         : BOOL;
  FSnapshotHandle      : THandle;
  FProcessEntry32      : TProcessEntry32;
  ListOfProcessOnMemory: TStringList;
  LArrIDandName        : TArray<string>;
  LProcessID           : cardinal;
  LProcessParentID     : cardinal;
  i, LCounter          : integer;
  LText                : string;
begin
  result := [];
  //
  ListOfProcessOnMemory := TStringList.Create;
  try
    FSnapshotHandle := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    try
      FProcessEntry32.dwSize := SizeOf(FProcessEntry32);
      Process32First(FSnapshotHandle, FProcessEntry32);
      //
      while Process32Next(FSnapshotHandle, FProcessEntry32) do
        ListOfProcessOnMemory.AddPair( { a=b=c }                                                                { }
          FProcessEntry32.th32ProcessID.ToString + '=' + FProcessEntry32.th32ParentProcessID.ToString,          { }
          FProcessEntry32.szExeFile);
      //
      Form1.Memo1.Lines.AddStrings(ListOfProcessOnMemory.ToStringArray);
    finally
      CloseHandle(FSnapshotHandle);
    end;
    //
    i        := ListOfProcessOnMemory.Count;
    LCounter := 0;
    //
    while (LCounter < i) do
      begin
        LArrIDandName := ListOfProcessOnMemory[LCounter].Split(['=']); // 3 items (a=b=c) above...
        //
        if (length(LArrIDandName) = 3) then
          begin
            LProcessID       := StrToIntDef(LArrIDandName[0], 0); // processID
            LProcessParentID := StrToIntDef(LArrIDandName[1], 0); // parentID
            //
            // if (LProcessID <> 0) then // "0" = "System"
            begin
              LText := SearchParentProcessFrom(LProcessParentID); // find parentID name
              //
              // parentID + processName
              if not(LText.IsEmpty) and LText.Contains(LArrIDandName[1]) and (LText.Contains(LArrIDandName[2])) then
                LText := LText + ' = load by itself';
              //
              Form1.Memo2.Lines.Add(LArrIDandName[0] + '=' + LArrIDandName[2] + ' Parent: ' + LArrIDandName[1] + '  -> Search: ' + LText);
            end;
          end;
        //
        LCounter := LCounter + 1;
      end;
  finally
    ListOfProcessOnMemory.Free;
  end;
end;

procedure TForm1.Btn_All_Process_On_MemoryClick(Sender: TObject);
begin
  CheckProcessOnMemory;
end;

procedure TForm1.Btn_Run_ME_againClick(Sender: TObject);
begin
  // run me again... by myself!
  ShellExecute(0, PWideChar(''), PWideChar('project1.exe'), PWideChar(''), PWideChar(''), sw_normal);
end;

 

 

 

 

bds_NUccFH5QCV.gif

Edited by programmerdelphi2k

Share this post


Link to post

Why not simple use a mutex and check at startup for its presence?
No need to do all that what @programmerdelphi2k wrote at all.
@programmerdelphi2k I suggest to switch to EnumProcesses(), it is way faster compared to CreateToolhelp32Snapshot().

 

Here's a small snippet example that you could run at startup
 

function AppStartedByItself: Boolean;
var
  dummy: THandle;
  FSA: SECURITY_ATTRIBUTES;
  FSD: SECURITY_DESCRIPTOR;
begin
  InitializeSecurityDescriptor(@FSD, SECURITY_DESCRIPTOR_REVISION);
  SetSecurityDescriptorDacl(@FSD, True, nil, False);
  FSA.lpSecurityDescriptor := @FSD;
  FSA.nLength := SizeOf(SECURITY_ATTRIBUTES);
  FSA.bInheritHandle := True;
  dummy := CreateMutexW(@FSA, True, PWideChar('Global\' + 'SomethingUnique'));
  Result := (GetLastError = ERROR_ALREADY_EXISTS);
end;

call this method and react on True (app was started by itself) or False (app was started not by itself)

 

//updated the result setting

Edited by KodeZwerg

Share this post


Link to post
27 minutes ago, KodeZwerg said:

No need to do all that what @programmerdelphi2k wrote at all.

as I said: the code is from internet... I just checked the ParentID!

of course, that exists API functions for better checking!

 

now test your code for:  as I did in my code above... same step-by-step

  1. Run the app as usual
  2. now, re-run this same app using 
  3. procedure TForm1.Btn_Run_ME_againClick(Sender: TObject);
    begin
      // run me again... by myself!
      ShellExecute(0, PWideChar(''), PWideChar('project1.exe'), PWideChar(''), PWideChar(''), sw_normal);
    end;
  4. now, on first app run your code again

 

summary: 

  • the 2 session app will be showing that it was load by itself!!! --> but the first app (session) was called by BDS.exe or Explorer,  and 2nd app was called by 1st App
  • then, your code is fail too!  only the 2nd session was loaded by itself ... the 1st session was loaded by BDS.exe!!!
  • each session has a ID distinct, because it is process distincts
  •  

chrome_Oqoo2wvmuT.gif

Edited by programmerdelphi2k

Share this post


Link to post

This method is not made to be run more than once.
Thats why I wrote, at startup to choose what to do after check.

Share this post


Link to post
function GetParentProcessID(const AProcessID: DWORD): DWORD;
var
  aProcesses: array[0..1023] of DWORD;
  cbNeeded, cProcesses, i: DWORD;
  hProcess: THandle;
  parentProcessID: DWORD;
begin
  Result := 0;
  // Get the list of process identifiers
  if not EnumProcesses(@aProcesses[0], SizeOf(aProcesses), cbNeeded) then
    Exit;

  // Calculate how many process identifiers were returned
  cProcesses := cbNeeded div SizeOf(DWORD);

  // Find the process with the given process ID and get its parent process ID
  for i := 0 to cProcesses - 1 do
  begin
    if aProcesses[i] = AProcessID then
    begin
      hProcess := OpenProcess(PROCESS_QUERY_INFORMATION  or PROCESS_VM_READ, False, AProcessID);
      if hProcess <> 0 then
      begin
        try
          if GetProcessId(hProcess) <> 0 then
            parentProcessID := GetProcessId(hProcess);
        finally
          CloseHandle(hProcess);
        end;
      end;
      Break;
    end;
  end;

  Result := parentProcessID;
end;

Heres a faster way to get Parent ProcessID.

  • Sad 1

Share this post


Link to post
On 7/6/2023 at 1:08 PM, KodeZwerg said:

Heres a faster way to get Parent ProcessID.

That code can't possibly work as you propose.  For one thing, you can't determine parent/child relationships using EnumProcesses().  It only gives you a flat array of all the running process IDs at that moment, there is no guarantee as to their order in the array.  Second, you are simply looking for the caller's passed-in PID in the array, and when found then you are opening a HANDLE to that process and querying its PID - the same PID that you already have!  GetProcessId() does not give you a parent PID.

 

The only APIs I am aware of that can provide you with a parent PID are:

  • CreateToolhelp32Snapshot() + Process32(First|Next)()
  • NtQueryInformationProcess()

 

So, for example, using Toolhelp32:

function GetParentProcessID(const AProcessID: DWORD): DWORD;
var
  hSnapshot: THandle;
  pe: PROCESSENTRY32;
begin
  Result := 0;
  hSnapshot := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
  if hSnapshot <> INVALID_HANDLE_VALUE then
  try
    pe.dwSize := SizeOf(dwSize);
    if Process32First(hSnapshot, pe) then
    repeat
      if pe.th32ProcessID = AProcessID then
      begin
        Result := pe.th32ParentProcessID;
        Exit;
      end;
    until not Process32Next(hSnapshot, pe);
  finally
    CloseHandle(hSnapshot);
  end;
end;

Or, using NTDLL:

function GetParentProcessID(const AProcessID: DWORD): DWORD;
var
  hProcess: THandle;
  pbi: PROCESS_BASIC_INFORMATION;
begin
  Result := 0;
  hProcess := OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, AProcessID);
  if hProcess <> 0 then
  try
    if NtQueryInformationProcess(hProcess, ProcessBasicInformation, @pbi, SizeOf(pbi), nil) = STATUS_SUCCESS then
      Result := DWORD(pbi.InheritedFromUniqueProcessId);
  finally
    CloseHandle(hProcess);
  end;
end;

 

Edited by Remy Lebeau
  • Thanks 1

Share this post


Link to post

Just remember that process IDs are not monotonic and could be reused if a process closes and another one starts.

@robertjohns are you trying to protect from launch under a debugger?

Edited by Fr0sT.Brutal

Share this post


Link to post
On 7/8/2023 at 3:29 AM, Remy Lebeau said:

So, for example, using Toolhelp32:


function GetParentProcessID(const AProcessID: DWORD): DWORD;
var
  hSnapshot: THandle;
  pe: PROCESSENTRY32;
begin
  Result := 0;
  hSnapshot := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
  if hSnapshot <> INVALID_HANDLE_VALUE then
  try
    pe.dwSize := SizeOf(dwSize);
    if Process32First(hSnapshot, pe) then
    repeat
      if pe.th32ProcessID = AProcessID then
      begin
        Result := pe.th32ParentProcessID;
        Exit;
      end;
    until not Process32Next(hSnapshot, pe);
  finally
    CloseHandle(hSnapshot);
  end;
end;

 

A mini small correction;

    pe.dwSize := SizeOf(pe);

 

  • Like 1

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×