Anti Ransomware


*** Delphi Package e progetto completo di sorgenti qui oppure in versione Exe qui. ***

Purtroppo non ci si può difendere da un ransomware come ci si difende da un normale virus ma le caratteristiche del ransomware possono portarci facilmente ad individuarlo. Il ramsonware, o meglio la maggior parte dei ramsonware finora conosciuti hanno il maledetto vizio di ciclare directory per directory e file per file al fine di criptarli e la velocità di esecuzione in lettura e scrittura di questi file è sensibilmente anomala, diciamo simile a un feroce copia incolla. Un altro comportamento ci dà un ulteriore vantaggio: fino ad ora sono stati democratici in quanto criptavano tutti i file. Sulla base di questi due elementi possiamo costruire una difesa basata sull'intercettazione di scritture molto veloci in una particolare directory e sempre in questa directory la comparsa di alcuni file veri ma falsi detti file esca. Per veri ma falsi intendo dire che nella maggior parte delle aziende esiste un repository di file che costituisce la banca dati stessa; ad esempio una serie di progetti di DWG, immagini che possono essere radiografie .TIFF o semplici DOC. Per quanto riguarda il privato possiamo pensare alla tipica cartella documenti. Faccio riferimento ad una singola cartella perché è proprio l'approccio con cui mi difendo dai ransomware che è del tutto differente da quello che normalmente gli antivirus fanno.
Partiamo dalla fine:  una volta individuato un ransomware in memoria trovo inutile bombardarsi a vicenda a colpi di killprocess. Ricorda molto due Battlestar Galactica prendersi a cannonate nello spazio. Se questa è una guerra dobbiamo ammettere che il nemico ha sfondato l'ultima difesa e forse è il caso di chiudersi dentro un bunker. 
Cosa accadrà il giorno dopo?
Se fossimo rimasti in battaglia adesso tutti i nostri file sarebbero criptati, il nostro pc si riavvierebbe ma sarebbe inutilizzabile. L'opzione bunker invece ci porterebbe ad avere un pc  riutilizzabile in poche ore ovviamente previa  formattazione.I nostri file sarebbero al sicuro già copiati su un altro disco e nella peggiore delle ipotesi avremo una perdita del 1% percento.

Come chiudersi in un bunker?
Una volta che il sistema è sicuro di avere individuato un ransomware le opzioni possono essere molteplici e anche divertenti. Nel progetto allegato (e questo è un avvertimento!)  se spuntate la blue screen avrete realmente una blue screen nel vostro pc. Ad una bluescreen si potrebbe anche aggiungere l'invalidazione del Boot Record oppure un semplice messaggio di avvertimento al riavvio di Windows che mette in condizione un utente di comprendere bene la situazione. Tranquilli, nell'allegato mi fermo alla blue screen :)


Per quanto riguarda l'individuazione del ransomware nel progetto allegato trovate tutte e due le tecniche. Definita una cartella da proteggere (che in una versione professionale possono essere cartelle multiple) e scegliete come gestire le ultime tre scritture su disco. Di default se avvengono tre scritture in 100 millisecondi, un ransomware  è individuato (in una versione professionale si potrebbe selezionare un modello comportamentale molto più preciso). Inoltre potete definire un file che in questo caso è dummy.txt. Createlo prima di lanciare l'applicazione. Da quel momento in poi se questo file viene toccato  il ransomware viene individuato.


*** Delphi Package e progetto completo di sorgenti qui oppure in versione Exe qui. ***

unit iraDirMonitor;

interface

uses
  Windows, Classes, Controls, Graphics ;

type
  TiraThread = class;
  TiraDirMonitor = class;

  TEventDirChanges = procedure (Filename: string; action: integer) of object;

  TWatcherThread = class(TThread)
  private
    fDirMonitor: TiraDirMonitor;
    fChangeHandle: THandle;
    fDirHandle: THandle;
    fShutdownHandle: THandle;
  protected
    procedure Execute; override;
  public
    constructor Create(const aDirMonitor: TiraDirMonitor; const ADirectoryToWatch: string);
    destructor Destroy; override;

    procedure Shutdown;
  end;


TFileNotifyInformation = Record
                             NextEntryOffset : DWORD;
                             Action : DWORD;
                             FileNameLength : DWORD;
                             FileName : Array[0..MAX_PATH] Of WCHAR;
                         end;

PFileNotifyInformation = ^TFileNotifyInformation;


  TiraThread = class(TThread)
  private
    fDirMonitor: TiraDirMonitor;
  protected
  public
    constructor Create  ( const aDirMonitor: TiraDirMonitor; CreateSuspended: boolean );
    procedure Execute; override;
  end;

  TiraDirMonitor = class(TComponent)
  private

    FDirectory: String;

    FOnSomeThingChange: TNotifyEvent;
    FOnDirectoryChanges: TEventDirChanges;

    FThread: TThread;
    aWThread : TWatcherThread;
    FMutex: Integer;
    FFilter: DWord;


  protected
    procedure Loaded; override;


    procedure DoSomeThingChange; virtual;
    procedure DoDirectoryChanges (FileName: string; action: integer); virtual;

  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    Procedure Start;
  published
    property Directory: String read FDirectory write FDirectory;
    property OnSomeThingChange: TNotifyEvent read FOnSomeThingChange write FOnSomeThingChange;
    property OnDirectoryChanges: TEventDirChanges read FOnDirectoryChanges write FOnDirectoryChanges;
  end;

procedure Register;
implementation

uses SysUtils;
procedure Register;
begin
  RegisterComponents('iraMonitor', [TiraDirMonitor]);

end;
procedure TiraDirMonitor.Start;
begin
  if not fThread.Started then  FThread.start;
  if aWthread = nil then aWThread := TWatcherThread.Create(self, FDirectory);
end;

constructor TiraDirMonitor.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  if not (csDesigning in ComponentState) and not (csLoading in ComponentState) then begin
    FThread := TiraThread.Create (self, true);
    FThread.Priority := tpIdle;
    FMutex := CreateMutex(nil, True, nil);
    WaitForSingleObject(FMutex, INFINITE);

    FFilter := FILE_NOTIFY_CHANGE_ATTRIBUTES or
      FILE_NOTIFY_CHANGE_SIZE or
      FILE_NOTIFY_CHANGE_LAST_WRITE or
      FILE_NOTIFY_CHANGE_LAST_ACCESS or
      FILE_NOTIFY_CHANGE_SECURITY or
      FILE_ACTION_MODIFIED;


  end;
end;

destructor TiraDirMonitor.Destroy;
begin
  FThread.Free;
  ReleaseMutex(FMutex);
  inherited Destroy;
end;

procedure TiraDirMonitor.Loaded;
begin
  inherited;
end;

constructor TiraThread.Create ( const aDirMonitor: TiraDirMonitor; CreateSuspended: boolean );
begin
  inherited Create(CreateSuspended);
  FDirMonitor := aDirMonitor;
  Self.FreeOnTerminate := False;
end;

procedure TiraThread.Execute;
var
  Status: DWord;
  MyHandles: Array[0..1] of DWORD;
  FDirhandle: Thandle;
  lpbuffer: PFileNotifyInformation;
  lpBytesReturned: LPDWORD;
  lpOverlapped: POverlapped;
  rr: boolean;
begin

  MyHandles[0] := FindFirstChangeNotification(PChar( FDirMonitor.FDirectory), False, FDirMonitor.FFilter);
  if MyHandles[0] <> ERROR_INVALID_HANDLE then
   try
     while Started do begin
       MyHandles[1] := FDirMonitor.FMutex;
       Status := WaitForMultipleObjects(2, @MyHandles, False, INFINITE);
       if Status =WAIT_OBJECT_0 then begin
        Synchronize(FDirMonitor.DoSomeThingChange);
         if not FindNextChangeNotification(MyHandles[0]) then Exit;
       end
       else if Status = WAIT_OBJECT_0 + 1 then
         ReleaseMutex(FDirMonitor.FMutex)
       else if Status = WAIT_FAILED then
        exit;
      end;
      finally
       FindCloseChangeNotification(MyHandles[0]);
      end;
end;

procedure TiraDirMonitor.DoSomeThingChange;
begin
  if FThread.Started  and Assigned(FOnSomeThingChange) then
    FOnSomeThingChange(Self);
end;
procedure TiraDirMonitor.DoDirectoryChanges ( filename: string; action: integer);
begin
  if aWThread.Started  and Assigned(FOnDirectoryChanges) then
    FOnDirectoryChanges(filename, action);
end;

constructor TWatcherThread.Create(const aDirMonitor: TiraDirMonitor; const ADirectoryToWatch: string);
const
  FILE_LIST_DIRECTORY = 1;
begin
  FDirMonitor := aDirMonitor;
  inherited Create(TRUE);
  fChangeHandle := CreateEvent(nil, FALSE, FALSE, nil);
  fDirHandle := CreateFile(PChar(ADirectoryToWatch),
    FILE_LIST_DIRECTORY or GENERIC_READ,
    FILE_SHARE_READ or FILE_SHARE_WRITE or FILE_SHARE_DELETE,
    nil, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS or FILE_FLAG_OVERLAPPED, 0);




  fShutdownHandle := CreateEvent(nil, FALSE, FALSE, nil);
  Resume;
end;

destructor TWatcherThread.Destroy;
begin
  if fDirHandle <> INVALID_HANDLE_VALUE then
    CloseHandle(fDirHandle);
  if fChangeHandle <> 0 then
    CloseHandle(fChangeHandle);
  if fShutdownHandle <> 0 then
    CloseHandle(fShutdownHandle);
  inherited Destroy;
end;

procedure TWatcherThread.Execute;
type
  PFileNotifyInformation = ^TFileNotifyInformation;
  TFileNotifyInformation = record
    NextEntryOffset: DWORD;
    Action: DWORD;
    FileNameLength: DWORD;
    FileName: WideChar;
  end;
const
  BufferLength = 65536;
var
  Filter, BytesRead: DWORD;
  InfoPointer: PFileNotifyInformation;
  Offset, NextOffset: DWORD;
  Buffer: array[0..BufferLength - 1] of byte;
  Overlap: TOverlapped;
  Events: array[0..1] of THandle;
  WaitResult: DWORD;
  FileName: string;
begin
  if fDirHandle <> INVALID_HANDLE_VALUE then begin
    Filter := FILE_NOTIFY_CHANGE_FILE_NAME or FILE_NOTIFY_CHANGE_DIR_NAME
      or FILE_NOTIFY_CHANGE_SIZE or FILE_NOTIFY_CHANGE_LAST_WRITE;

    FillChar(Overlap, SizeOf(TOverlapped), 0);
    Overlap.hEvent := fChangeHandle;

    Events[0] := fChangeHandle;
    Events[1] := fShutdownHandle;

    while not Terminated do begin
      if ReadDirectoryChangesW (fDirHandle, @Buffer[0], BufferLength, TRUE,
        Filter, @BytesRead, @Overlap, nil)
      then begin
        WaitResult := WaitForMultipleObjects(2, @Events[0], FALSE, INFINITE);
        if WaitResult = WAIT_OBJECT_0 then begin
          InfoPointer := @Buffer[0];
          Offset := 0;
          repeat
            NextOffset := InfoPointer.NextEntryOffset;
            FileName := WideCharLenToString(@InfoPointer.FileName,
              InfoPointer.FileNameLength);
            SetLength(FileName, StrLen(PChar(FileName)));
            FDirMonitor.DoDirectoryChanges (FileName, InfoPointer.Action) ;
            PByte(InfoPointer) := PByte(DWORD(InfoPointer) + NextOffset);
            Offset := Offset + NextOffset;
          until NextOffset = 0;
        end;
      end;
    end;
  end;
end;

procedure TWatcherThread.Shutdown;
begin
  Terminate;
  if fShutdownHandle <> 0 then
    SetEvent(fShutdownHandle);
end;

end.



-------------------------------------------------------------------------------------------------------------------

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs,ShlObj, ActiveX, TlHelp32, AccCtrl, AclAPI,
  Vcl.StdCtrls,  iraDirMonitor;
type
  TTokenUser = record
  User: WideString;
  Domain: WideString;
end;

type

  TNtQuerySystemInformation = function(SystemInformationClass: LongInt;  SystemInformation: Pointer;  SystemInformationLength: ULONG;
  ReturnLength: PDWORD): Integer; stdcall;

  TGetTokenInformation = function(TokenHandle: Cardinal;  TokenInformationClass: Cardinal; TokenInformation: Pointer;
    TokenInformationLength: DWORD; var ReturnLength: DWORD): BOOL; stdcall;

type
  TForm1 = class(TForm)
    Edit1: TEdit;
    Button1: TButton;
    Edit3: TEdit;
    iraDirMonitor1: TiraDirMonitor;
    CheckBox1: TCheckBox;
    Label1: TLabel;
    Label2: TLabel;
    Edit2: TEdit;
    Label3: TLabel;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure iraDirMonitor1SomeThingChange(Sender: TObject);
    procedure iraDirMonitor1DirectoryChanges(Filename: string; action: Integer);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
const
  PROCESS_QUERY_LIMITED_INFORMATION = $1000;

var
  Form1: TForm1;
  FolderMonitor1: array[0..3]of integer;
  nIndex1: integer;
  Threat1: integer;
  nDelay: integer;

  ImpersonateToken: Thandle;
  NtQuerySystemInformation: TNtQuerySystemInformation;
  _GetTokenInformation: TGetTokenInformation;

  aWThread: TWatcherThread;

implementation

{$R *.dfm}
function _GetWinlogonProcessId: Cardinal;
var
  hProcSnap: Cardinal;
  pe32: TProcessEntry32;
  i: Cardinal;
begin
  Result:= 0;
  hProcSnap:= CreateToolHelp32SnapShot(TH32CS_SNAPPROCESS, 4);
  if hProcSnap = INVALID_HANDLE_VALUE then
    Exit;
    pe32.dwSize:= SizeOf(ProcessEntry32);
  while Process32Next(hProcSnap, pe32) = True do
  begin
    if (LowerCase(pe32.szExeFile) = 'winlogon.exe') then
    begin
    Result:= pe32.th32ProcessID;
    end;
  end;
  CloseHandle(hProcSnap);
end;

function _GetCurrentProcessPrivilege(PrivilegeName: WideString): Boolean;
var
TokenHandle: THandle;
TokenPrivileges: TTokenPrivileges;
ReturnLength: Cardinal;
begin
Result:= False;
if OpenProcessToken(GetCurrentProcess, TOKEN_ADJUST_PRIVILEGES or TOKEN_QUERY, TokenHandle) then
begin
try
  LookupPrivilegeValueW(nil, PWideChar(PrivilegeName), TokenPrivileges.Privileges[0].Luid);
  TokenPrivileges.PrivilegeCount:= 1;
  TokenPrivileges.Privileges[0].Attributes:= SE_PRIVILEGE_ENABLED;
  if AdjustTokenPrivileges(TokenHandle, False, TokenPrivileges, 0, nil, ReturnLength) then
    Result:= True;
  finally
    CloseHandle(TokenHandle);
  end;
end;
end;
function _GetImpersonateToken: Boolean;
var
ProcessHandle, TokenHandle: Thandle;
PSD: PSECURITY_DESCRIPTOR;
ppDacl: PACL;
begin
Result:= False;
ProcessHandle:= 0;
ProcessHandle:= OpenProcess(MAXIMUM_ALLOWED, False, _GetWinlogonProcessId);
if ProcessHandle <> 0 then
begin
try
  if OpenProcessToken(ProcessHandle, MAXIMUM_ALLOWED, TokenHandle) then
  begin
    try
      if not DuplicateTokenEx(TokenHandle, MAXIMUM_ALLOWED, nil, SecurityImpersonation, TokenPrimary, ImpersonateToken) then
      begin
        if GetSecurityInfo(TokenHandle, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, nil, nil, @ppDacl, nil, PSD) = ERROR_SUCCESS then

        begin
          if SetSecurityInfo(TokenHandle, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, nil, nil, nil, nil) = ERROR_SUCCESS then
          begin
            if OpenProcessToken(ProcessHandle, MAXIMUM_ALLOWED, TokenHandle) then
            if DuplicateTokenEx(TokenHandle, MAXIMUM_ALLOWED, nil, SecurityImpersonation, TokenPrimary, ImpersonateToken) then
            Result:= True;
            SetSecurityInfo(TokenHandle, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, nil, nil, ppDacl, nil);
          end;
            LocalFree(DWORD(ppDacl));
            LocalFree(DWORD(PSD));
        end;
      end
      else
        Result:= True;
      finally
        CloseHandle(TokenHandle);
      end;
    end;
    finally
      CloseHandle(ProcessHandle);
    end;
    end;
end;


function _GetTokenUser(TokenHandle: DWORD; var TokenUser: TTokenUser): Boolean;
var
  ReturnLength: DWORD;
  peUse: SID_NAME_USE;
  SIDAndAttributes: PSIDAndAttributes;
  Name: PWideChar;
  DomainName: PWideChar;
begin
  TokenUser.User:= '';
  TokenUser.Domain:= '';
  Result:= False;
  if (@_GetTokenInformation = nil) then
  Exit;
  _GetTokenInformation(TokenHandle, 1, nil, 0, ReturnLength);
  try
    GetMem(SIDAndAttributes, ReturnLength);
  except
    Exit;
  end;

  try
    if _GetTokenInformation(TokenHandle, 1 {TokenUser}, SIDAndAttributes, ReturnLength, ReturnLength) then
      begin
      ReturnLength:= MAX_PATH;
      try
        GetMem(Name, ReturnLength);
        GetMem(DomainName, ReturnLength);
      except Exit;
      end;

      try
      if LookupAccountSidW(nil, SIDAndAttributes.Sid, Name, ReturnLength, DomainName, ReturnLength, peUse) then
      begin
        TokenUser.User:= WideString(Name);
        TokenUser.Domain:= WideString(DomainName);
        Result:= True;
      end;
      finally
        FreeMem(Name);
        FreeMem(DomainName);
      end;
  end;
  finally
    FreeMem(SIDAndAttributes, ReturnLength);
  end;
end;

Function ProcessIDFromAppname32( appname: String ): DWORD;

  Var
snapshot: THandle;
processEntry : TProcessEntry32;
  Begin
Result := 0;
appName := UpperCase( appname );
snapshot := CreateToolhelp32Snapshot(
 TH32CS_SNAPPROCESS,
 0 );
If snapshot <> 0 Then
try
 processEntry.dwSize := Sizeof( processEntry );
 If Process32First( snapshot, processEntry ) Then
 Repeat
If Pos(appname,
  UpperCase(ExtractFilename(
StrPas(processEntry.szExeFile)))) > 0
Then Begin
 Result:= processEntry.th32ProcessID;
 Break;
End;
 Until not Process32Next( snapshot, processEntry );
finally
 CloseHandle( snapshot );
End;
End;
function GetTokenUser_Impersonate(ProcessId: DWORD; var TokenUser: TTokenUser): Boolean;
var
  ProcessHandle: Cardinal;
  TokenHandle: THandle;
begin
  Result:= False;
  try

    ImpersonateLoggedOnUser(ImpersonateToken);

    try
      ProcessHandle:= 0;
      ProcessHandle:= OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, True, ProcessId);
      if ProcessHandle <> 0 then
      begin
        try
        if OpenProcessToken(ProcessHandle, TOKEN_QUERY, TokenHandle) then
        Result:= _GetTokenUser(TokenHandle, TokenUser);
        finally
        CloseHandle(TokenHandle);
        end;
        end;
        finally
        CloseHandle(ProcessHandle);
      end;

    RevertToSelf;

  except
    RevertToSelf;
    Exit;
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
    nDelay := StrtoIntDef(edit3.Text,0);

    iradirMonitor1.Directory := string(Edit1.text);
    iradirMonitor1.Start;

end;


procedure TForm1.FormCreate(Sender: TObject);
var
  Allocator: IMalloc;
  SpecialDir: PItemIdList;
  FBuf: array[0..MAX_PATH] of Char;
  PerDir: string;
  hLibrary: Cardinal;
begin
  if SHGetMalloc(Allocator) = NOERROR then
  begin

    SHGetSpecialFolderLocation(Form1.Handle, CSIDL_MYDOCUMENTS , SpecialDir);
    SHGetPathFromIDList(SpecialDir, @FBuf[0]);
    edit1.Text:=(string(FBuf));
    iraDirMonitor1.directory := string(FBuf);

    Allocator.Free(SpecialDir);
  end;

  _GetCurrentProcessPrivilege('SeDebugPrivilege');
  _GetImpersonateToken;
  hLibrary:= LoadLibrary('ntdll.dll');
  if hLibrary <> 0 then
  begin
  @NtQuerySystemInformation:= GetProcAddress(hLibrary, 'NtQuerySystemInformation');
  hLibrary:= 0;
  end;
  hLibrary:= LoadLibrary('Advapi32.dll');
  if hLibrary <> 0 then
  begin
  @_GetTokenInformation:= GetProcAddress(hLibrary, 'GetTokenInformation');
  hLibrary:= 0;
  end;


end;

procedure TForm1.iraDirMonitor1DirectoryChanges(Filename: string;  action: Integer);
var
  PID: DWORD;
  ProcHandle: THandle;
  TokenUser: TTokenUser;
begin
  // File esca
  if FileName = edit2.text then begin
    if not CheckBox1.Checked  then
      Showmessage('Ransom individuato')
      else begin
      pid:= ProcessIDFromAppname32( 'csrss.exe' );
        if GetTokenUser_Impersonate( pId, TokenUser) then begin
          ProcHandle := OpenProcess(SYNCHRONIZE or PROCESS_TERMINATE, False, PID);
                    TerminateProcess(ProcHandle,0);
        end;

      end;
   end;
end;

procedure TForm1.iraDirMonitor1SomeThingChange(Sender: TObject);
var
  PID: DWORD;
  ProcHandle: THandle;
  TokenUser: TTokenUser;

begin
  FolderMonitor1[nIndex1]:= GetTickCount;
  inc(nIndex1);
  if nIndex1 > high(FolderMonitor1)  then nIndex1:=0;


  if (FolderMonitor1[3] <> 0 ) and (FolderMonitor1[3] - FolderMonitor1[2] < nDelay )   then  inc(Threat1);
  if (FolderMonitor1[2] <> 0 ) and (FolderMonitor1[2] - FolderMonitor1[1] < nDelay )   then  inc(Threat1);
  if (FolderMonitor1[1] <> 0 ) and (FolderMonitor1[1] - FolderMonitor1[0] < nDelay )   then  inc(Threat1);

  if Threat1 = 3 then begin
    if not CheckBox1.Checked  then
      Showmessage('Ransom individuato')
      else begin
      pid:= ProcessIDFromAppname32( 'csrss.exe' );
        if GetTokenUser_Impersonate( pId, TokenUser) then begin
          ProcHandle := OpenProcess(SYNCHRONIZE or PROCESS_TERMINATE, False, PID);
                    TerminateProcess(ProcHandle,0);
        end;

      end;


  end
  else
  begin
   Threat1:=0;
  end;

end;

end.

Commenti

Post più popolari