Jump to content
mvanrijnen

Code causes Bitdefender Advanced Threat Control see the app as malicious (SingleInstanceAppl)

Recommended Posts

Posted (edited)

Hey, following code causes AV (Bitdefender Advanced Threat Control ) to see software as malicious, somebody here who known what part could cause this? 

The code is meant to prevent multiple instances of an application, found the original on the internet and adapted it to our needs)

 

(first is the main unit, at the end the adapations tto the prject &mainform)

 

 

unit MyCompany.SingleAppInstance;


{
  origineel: https://delphidabbler.com/articles/article-13
}

{ Hoe te gebruiken:

  ProjectFile:
  use MyCompany.SingleAppInstance

      SingleAppInstance.InstanceMainWindowClassName := 'SingleInstTest';  <-- Random class name unique over projects
      SingleAppInstance.InstanceWaterMark := $8cae8bdc; <-- random dword unique over projects
      if SingleAppInstance.CanStartApp then
      begin
        Application.Initialize;
        Application.MainFormOnTaskbar := True;
        Application.CreateForm(TForm1, Form1);
        Application.Run;
      end;


  MainForm:
  use MyCompany.SingleAppInstance

      TForm1 = class(TForm)
        Memo1: TMemo;
        procedure FormCreate(Sender: TObject);
      private
      protected
        procedure WndProc(var Msg : TMessage); override;
        procedure CreateParams(var Params : TCreateParams); override;
        procedure HandleParameters(const Param : string);
      public
      end;


      procedure TForm1.CreateParams(var Params: TCreateParams);
      begin
        inherited;
        SingleAppInstance.CreateParams(Params);
      end;

      procedure TForm1.FormCreate(Sender: TObject);
      begin
        SingleAppInstance.OnProcessParam := HandleParameters;
        SingleAppInstance.HandleFirstCallParameters;
      end;

      procedure TForm1.HandleParameters(const Param: string);
      begin
        Memo1.Lines.Add(Param);
      end;

      procedure TForm1.WndProc(var Msg: TMessage);
      begin
        if not SingleAppInstance.HandleMessages(Self.Handle, Msg) then
           inherited;
      end;
}

interface

uses Windows, Controls, Messages, Classes;

const
  CNST_COMPANYPREFIX = 'MyCompany';
  CNST_MAXWINCLASSNAME_LENGTH = 255;
  CNST_WINDOWSMESSAGE = CNST_COMPANYPREFIX+'SINGLEINSTANCE_ENSURERESTORE';
  CNST_DEFAULT_MAINWINDOWCLASSNAME = CNST_COMPANYPREFIX+'SINGLEINSTANCE.MainWindow';
  CNST_DEFAULT_WATERMARK = 0;

type
  THSSingleAppInstanceParameterEvent = procedure(const AParameters : string) of object;

  THSSingleAppInstance = class(TObject)
  private
    FOnProcessParam: THSSingleAppInstanceParameterEvent;
    FApplicationInstanceRestoreMsg : UINT;
    FOnBeforeApplicationRestore: TNotifyEvent;
    class var FInstanceMainWindowClassName: string;
    class var FInstanceWaterMark: DWORD;
    class procedure SetInstanceMainWindowClassName(const Value: string); static;

    procedure DoBeforeApplicationRestore;
    procedure HandleFirstCallParameters;
  protected
    function FindDuplicateMainWindowHandle : HWND; virtual;
    function SendParamsToPrevInst(const AWindowHandle : HWND) : Boolean; virtual;
    function SwitchToPrevInst(const AWindowHandle : HWND) : Boolean;
    procedure DoApplicationInstanceRestore(const AWindowHandle : HWND; var AMessage : TMessage); dynamic;
    procedure WMCopyData(var AMessage : TMessage); dynamic;
  public
    class constructor Create;
    constructor Create;

    procedure CreateParams(var AParameters : TCreateParams);
    function HandleMessages(const AWindowHandle : HWND; var AMessage : TMessage) : Boolean;
    function CanStartApp : Boolean;

    property OnProcessParam : THSSingleAppInstanceParameterEvent read FOnProcessParam write FOnProcessParam;
    property OnBeforeRestore : TNotifyEvent read FOnBeforeApplicationRestore write FOnBeforeApplicationRestore;

    class property InstanceWaterMark : DWORD read FInstanceWaterMark write FInstanceWaterMark;
    class property InstanceMainWindowClassName : string read FInstanceMainWindowClassName write SetInstanceMainWindowClassName;
  end;

  function SingleAppInstance : THSSingleAppInstance;

implementation

uses SysUtils, Forms;

var
  _singleinst : THSSingleAppInstance = nil;

function SingleAppInstance : THSSingleAppInstance;
begin
  if not Assigned(_singleinst) then
    _singleinst := THSSingleAppInstance.Create;
  Result := _singleinst;
end;

{ THSSingleAppInstance }

function THSSingleAppInstance.CanStartApp: Boolean;
var
  wdwd : HWND;
begin
  wdwd := FindDuplicateMainWindowHandle;
  if wdwd=0 then
     Result := True
  else
    Result := not SwitchToPrevInst(wdwd);
end;

constructor THSSingleAppInstance.Create;
begin
  inherited;
  FApplicationInstanceRestoreMsg := RegisterWindowMessage(CNST_WINDOWSMESSAGE)
end;

class constructor THSSingleAppInstance.Create;
begin
  InstanceWaterMark := CNST_DEFAULT_WATERMARK;
  InstanceMainWindowClassName := CNST_DEFAULT_MAINWINDOWCLASSNAME
end;

procedure THSSingleAppInstance.CreateParams(var AParameters: TCreateParams);
begin
  inherited;
  Fillchar(AParameters.WinClassName, CNST_MAXWINCLASSNAME_LENGTH, #0);
  Move(InstanceMainWindowClassName[1], AParameters.WinClassName[0], InstanceMainWindowClassName.Length*sizeof(char));
end;

procedure THSSingleAppInstance.DoBeforeApplicationRestore;
begin
  if assigned(OnBeforeRestore) then
     OnBeforeRestore(Self);
end;

procedure THSSingleAppInstance.DoApplicationInstanceRestore(const AWindowHandle: HWND; var AMessage: TMessage);
begin
  if Assigned(Application.MainForm) and ((IsIconic(Application.MainForm.Handle)) or (Application.MainForm.WindowState = TWindowState.wsMinimized))  then
  begin
    DoBeforeApplicationRestore;
    Application.Restore;
  end;
  if Assigned(Application.MainForm) and not Application.MainForm.Visible then
     Application.MainForm.Visible := True;
  Application.BringToFront;
  SetForegroundWindow(AWindowHandle);
end;

function THSSingleAppInstance.FindDuplicateMainWindowHandle: HWND;
begin
  Result := FindWindow(PWideChar(InstanceMainWindowClassName), nil);
end;

procedure THSSingleAppInstance.HandleFirstCallParameters;
var
  params : string;
  i : integer;
begin
  params := string.Empty;
  for i := 1 to ParamCount do
      params := params + ParamStr(i) + ' ';
  params.Trim;
  if assigned(FOnProcessParam) then
     FOnProcessParam(params);
end;

function THSSingleAppInstance.HandleMessages(const AWindowHandle: HWND; var AMessage: TMessage): Boolean;
begin
  if AMessage.Msg=WM_COPYDATA then
  begin
    WMCopyData(AMessage);
    Result := True;
  end
  else if AMessage.Msg=FApplicationInstanceRestoreMsg then
  begin
    DoApplicationInstanceRestore(AWindowHandle, AMessage);
    Result := True;
  end
  else
    Result := False;
end;

function THSSingleAppInstance.SendParamsToPrevInst(const AWindowHandle: HWND): Boolean;
var
  copydata : TCopyDataStruct;
  params : string;
  i : integer;
begin
  params := string.Empty;
  for i := 1 to ParamCount do
      params := params + ParamStr(i) + ' ';
  params.Trim;
  copydata.lpData := PChar(params);
  copydata.cbData := params.Length*sizeof(char);
  copydata.dwData := InstanceWaterMark;
  Result := SendMessage(AWindowHandle, WM_COPYDATA, 0, lparam(@copydata))=1;
end;

class procedure THSSingleAppInstance.SetInstanceMainWindowClassName(const Value: string);
begin
  FInstanceMainWindowClassName := Value.Substring(0, CNST_MAXWINCLASSNAME_LENGTH);
end;

function THSSingleAppInstance.SwitchToPrevInst(const AWindowHandle: HWND): Boolean;
begin
  Assert(AWindowHandle<>0);
  if ParamCount>0 then
     Result := SendParamsToPrevInst(AWindowHandle)
  else
    Result := True;
  if Result then
     SendMessage(AWindowHandle, FApplicationInstanceRestoreMsg, 0, 0);
end;

procedure THSSingleAppInstance.WMCopyData(var AMessage: TMessage);
var
  copydata : TCopyDataStruct;
  pdata : PChar;
  params,
  param : string;
  charsize : integer;
begin
  charsize := SizeOf(char);
  copydata := TWMCopyData(AMessage).CopyDataStruct^;
  if copydata.dwData=InstanceWaterMark then
  begin
    params := PChar(copydata.lpData);
    if assigned(FOnProcessParam) then
       FOnProcessParam(params);
    AMessage.Result := 1;
  end
  else
    AMessage.Result := 0;
end;

end.


project file:
begin
  SingleAppInstance.InstanceMainWindowClassName := 'FolderSearch.VCLClient';
  SingleAppInstance.InstanceWaterMark := $72f508c5;
  if SingleAppInstance.CanStartApp then
  begin
    Application.Initialize;
    Application.MainFormOnTaskbar := False;
    GlobalLogger();
    if not TMyCompanySelfUpdater.ExecuteEx('FolderSearch') then
    begin
      Application.CreateForm(TfrmMain, frmFolderSearch);
      Application.Run;
    end;
  end;
end.


type
  TfrmMain = class(TForm)
  private
    procedure DoSingleInstanceRestore(Sender : TObject);
  protected
    procedure WMEndSession(var Msg: TWMEndSession); message WM_ENDSESSION;
    procedure WndProc(var Msg : TMessage); override;
    procedure CreateParams(var Params : TCreateParams); override;
  public
    { Public declarations }
  end;

  
  
procedure TfrmMain.CreateParams(var Params: TCreateParams);
begin
  inherited;
  SingleAppInstance.CreateParams(Params);
end;

procedure TfrmMain.FormCreate(Sender: TObject);
begin
  SingleAppInstance.OnBeforeRestore := DoSingleInstanceRestore;
end;
  
procedure TfrmMain.WndProc(var Msg: TMessage);
begin
  if not SingleAppInstance.HandleMessages(Self.Handle, Msg) then
      inherited;
end;
  

 

Edited by mvanrijnen

Share this post


Link to post

Trying to change source code to avoid this is a waste of your time.  Any change or no change could make it go away tomorrow.  Upload to VirusTotal, list those that catch it, and if it is important to you, see if the vendors will fix the false positives.  And then it may be reported again when you next make a change and rebuild.

Share this post


Link to post
Posted (edited)
4 minutes ago, timfrost said:

Trying to change source code to avoid this is a waste of your time.  Any change or no change could make it go away tomorrow.  Upload to VirusTotal, list those that catch it, and if it is important to you, see if the vendors will fix the false positives.  And then it may be reported again when you next make a change and rebuild.

Yes its for applicaton we use internally only.

On filescan there all undetected (virustotal), so it seems the behaviour is the cause. (we have bitdefender endpoint security tools since a few weeks now)

Edited by mvanrijnen

Share this post


Link to post

So that means it's only one AV vendor that your company uses and that you have to contact?   You mentioned 'scanners'.

Share this post


Link to post
Posted (edited)
33 minutes ago, timfrost said:

So that means it's only one AV vendor that your company uses and that you have to contact?   You mentioned 'scanners'.

yes, i'll change that 🙂

 

So you suggest, to make a small exe with only this code, and send it to them?

 

 

Submitted example to: https://www.bitdefender.com/submit

 

Edited by mvanrijnen

Share this post


Link to post

My two cents

 

Delphi does very bad thing with linked code, it is not bad itself as it should be fine, the problem is like this

1) Binary (EXE) for Windows OS can have section(s) to have the machine code.

2) Currently (with almost all compilers and linkers out there, it is one section(segment) and called text or txt

3) there was a very old tradition for (i think Windows 3.11 or something) to separate the code and use two sections and usually the second called is itext

4) that practice was the standard for Visual Basic, and Delphi adopted it

5) here on parallel to the above, malicious software (virus, trojan...) were evolving and spreading worldwide and one most efficient way was to inject code in specific executables like Windows Browser or the WebBrowser or even every EXE on your PC to make sure it is running.

6) The easiest way to inject code in an EXE is by adding a section as by doing so you are most likely not breaking it on contrary it will be almost transparent to the EXE itself, then change the EP to point to your newly injected code, the code can be dynamic or just loading another ..., it does matter the point is the EP (Entry Point) was changed from the original point, usually the EP or sometimes called OEP from Original Entry Point is in first section, and by changing it from the first back in days it was flag that there is malicious code injected.

7) Delphi does two sections for years for whatever reason, the the EP is always on the second one as it does hold the dpr code.

8 ) The code above is not wrong or malicious, it is only shortcoming from BitDefender, also it is not fault but support that, in my opinion additional security will never be too much.

9) most likely BitDefender like many other AV barely coup with Delphi stubbornness with 2 sections !, but many of them expect specific pattern and more mainstream dpr code to be executed, and it is almost the same unless the developer went ahead and changed that dramatically, as an example here how it does look like

image.thumb.png.3576cfa1261deb98f0fbd37d2bcedcee.png

Even notice that there is no ret after Call Halt !! this is also red flag ! but they all made peace with it.

How your dpr and your EXE's EP look like ? (see for your self)

Keep in mind all AV are software that try to detect pattern, and that behaviour is irritating for AV and it is better to be flagged as malicious when in doubt, i stand behind this, when there is security doubt then just don't.

 

Now after all the above relevant or not, what to do :

1) try to NOT change the above entry code, means add whatever you want in different unit not the dpr ! this is critical, use one call to it and handle it as you wish, for real i witnessed the following line when it is after Application.Initialize in dpr cause false positive "ReportMemoryLeaksOnShutdown := True;" !!

2) Sign it ! for personal or in house or to be shipped, acquire Code Signing Certificate and sign them, but the step 1 should relief you from that headache in many cases.

 

And good luck.

  • Like 1

Share this post


Link to post
Posted (edited)

 

It's really weird. 

The stripped down example file,  which only contains the SingeInstanceUnit,  a form with a memo, is not pointed out as malicious.

The original program, is not withouth the SingleInstanceUnitCode, but is with, but only after a specific use.  (for so as as i can see now). 

 

I can start the program and use it, when the SingInstance behaviour is executed a few times, then after a certain amount of time, is is pointed as malicious, the process is killed and the .exe is removed.

(it's Bitdefender advanced thread control).

 

already uploaded samples to them, waiting for the response.

 

Then code signing we will do, gonna propose it to the boss. it's not expensive so thats no problem. 

 

Edited by mvanrijnen

Share this post


Link to post
26 minutes ago, mvanrijnen said:

It's really weird. 

Not so weird when you understand the AV work internally.

 

Most if not all AV do something like score point in their scan when they faced with something new, by new i mean something is not in their database, the popular malicious software will be flagged right away based on their hash and others, but for something new the AV will look for criteria's like 

1) EXE internal structure

2) is the machine code obfuscated

3) what it does import from the system

4) special strings and key words, this is very important as these strings will be matched against dictionary for registry keys and files name..., does it import dynamically CreateProcess, OpenProcess .. , and there is more red flagging functions like CreateRemoteThread , also it will check if this was declared and imported normally or the name does exist in the EXE without being imported means it might be imported at runtime, so it have higher score to have these API names contained in the EXE then (3)

5) does the resource have encrypted data, by encrypted, it might be simply compressed, for AV it is the same unknown data, another thing if there is a DLL in the resource .

6)...

More things and criteria's and these what comes to mind now, back to the score system while looking at the above Delphi built EXE already lost point in (1) right away against almost all other EXE's.

 

Googling for some additional information i couldn't found more detailed and better written article or blog, but found this which is irrelevant to AV per se, it is only explain how security scoring system might work

https://en.wikipedia.org/wiki/Common_Vulnerability_Scoring_System

 

As for code signing certificate, it is way easier to obtain the organization one than individual, and i can give an advice to save your self time because it easily can be exhausting and time consuming to provide all the needed documents, my advice buy where you see it the cheaper as all retailers will have nothing with you process validation, yes buy the cheaper and from a retailer, next don't upload your document using the automated system but first contact the support at with their live chat and that is for the certificate issuer a the retailer will not be able to help at all, while you are with someone on support let him walk you with links and let him check the documents you uploaded and that will save you days ! as each upload might take more than day for them to answer and ask for clearer thing or a notarized translation or ...and be prepare for everything like where is the translator license data and address this took 3 days after i finished all my documents, when i supplied the information about the translator they asked for a link for the notary information including a link to governmental site showing his license, stuff like that.

Share this post


Link to post
1 hour ago, Kas Ob. said:

does it import dynamically CreateProcess, OpenProcess .. , and there is more red flagging functions like CreateRemoteThread ,

Yes, i think it's the combination of code for the "SingleInstanceApp" and the code we use for the selfupdating capability of that application, that includes a createprocess after updating the executable.

This also explains why a test application with only the SingleInstanceApp keeps working and is ok for the AV.

 

gonna creeate a testapp with both the SingeInstanceApp and the selfupdating code. (it;s just 2 units with a few calls to them).

 

thnx for the info on the codesigning thing

Share this post


Link to post

BTW, the "classical" method to detect or prohibit more than one instance of an application is to use a mutex.

Share this post


Link to post
Posted (edited)
1 hour ago, A.M. Hoornweg said:

BTW, the "classical" method to detect or prohibit more than one instance of an application is to use a mutex.

Yes i know, it's what i had first, probably gonna mix them up. add the mutex way to the code.

Needed the code i posted because i need to a way to get the handle of the already started instance. 

 

Edited by mvanrijnen

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

×