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.
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.
-------------------------------------------------------------------------------------------------------------------
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
Posta un commento