344 lines
9.8 KiB
Plaintext
344 lines
9.8 KiB
Plaintext
unit Bs1ContentsFlowPolicyUnit;
|
|
|
|
interface
|
|
|
|
uses
|
|
Winapi.Windows, System.SysUtils, System.Classes, System.JSON, System.IOUtils,
|
|
System.Generics.Collections, System.StrUtils,
|
|
BsoneDebug;
|
|
|
|
type
|
|
|
|
TDeviceType = ( DNONE, COMMON, REMOVALBE, EXTERNALHDD, NETWORKDRIVEOUT, CDROM, MTP, BLUETOOTH, FLOPPYDISK );
|
|
|
|
TContentsDeviceType = record
|
|
name: string;
|
|
edeviceType: TDeviceType;
|
|
end;
|
|
|
|
TDeviceControlPolicy = record
|
|
policy_: Integer;
|
|
fileSize_: Integer;
|
|
dump_: Integer;
|
|
end;
|
|
|
|
TProcessPolicy = class
|
|
private
|
|
deviceTypes_: TArray<string>;
|
|
exceptionPath_: TArray<string>;
|
|
extension_: TArray<string>;
|
|
exceptionExtension_: TArray<string>;
|
|
mode_: Integer;
|
|
|
|
function CompareExtension(const AListExtension, AInputExtension: string): Boolean;
|
|
public
|
|
constructor CreateFromJSON(AJSONObject: TJSONObject);
|
|
|
|
function IsExceptionPath(const APath: string): Boolean;
|
|
function IsExtension(const AExt: string): Boolean;
|
|
function IsExceptionExtension(const AExt: string): Boolean;
|
|
function HasDeviceType(const ADeviceType: string): Boolean;
|
|
|
|
property DeviceTypes: TArray<string> read deviceTypes_;
|
|
property Mode: Integer read mode_;
|
|
property ExceptionPath: TArray<string> read exceptionPath_;
|
|
property Extension: TArray<string> read extension_;
|
|
property ExceptionExtension: TArray<string> read exceptionExtension_;
|
|
end;
|
|
|
|
TPolicyManager = class
|
|
private
|
|
dataFlowSeq_: Integer;
|
|
globalExceptionPath_: TArray<string>;
|
|
deviceControls_: TDictionary<string, TDeviceControlPolicy>;
|
|
currentProcessPolicy_: TProcessPolicy;
|
|
isHookingTarget_: Boolean;
|
|
currentNormalizedName_: string;
|
|
|
|
policyPath_: string;
|
|
processName_: string;
|
|
cs: TRTLCriticalSection;
|
|
|
|
// 헬퍼 함수들
|
|
class function ParseStringArray(AJSONValue: TJSONValue): TArray<string>;
|
|
class function ParseStringOrArray(AJSONValue: TJSONValue): TArray<string>;
|
|
class function NormalizeProcessName(const AName: string): string;
|
|
public
|
|
constructor Create(const policyPath: string; const processName: string);
|
|
destructor Destroy; override;
|
|
|
|
procedure GetPolicy;
|
|
|
|
property IsHookingTarget: Boolean read isHookingTarget_;
|
|
property CurrentProcessPolicy: TProcessPolicy read currentProcessPolicy_;
|
|
property GlobalExceptionPath: TArray<string> read globalExceptionPath_;
|
|
property DataFlowSeq: Integer read dataFlowSeq_;
|
|
property DeviceControls: TDictionary<string, TDeviceControlPolicy> read deviceControls_;
|
|
function GetDeviceControlPolicy(const ADeviceName: string; out APolicy: TDeviceControlPolicy): Boolean;
|
|
function IsExceptionPath(const APath: string): Boolean;
|
|
|
|
end;
|
|
|
|
implementation
|
|
|
|
{ TProcessPolicy }
|
|
|
|
constructor TProcessPolicy.CreateFromJSON(AJSONObject: TJSONObject);
|
|
begin
|
|
inherited Create;
|
|
// deviceType은 문자열 하나일 수도, 배열일 수도 있음 -> 헬퍼로 통일 처리
|
|
deviceTypes_ := TPolicyManager.ParseStringOrArray(AJSONObject.GetValue('deviceType'));
|
|
mode_ := AJSONObject.GetValue<Integer>('mode');
|
|
|
|
exceptionPath_ := TPolicyManager.ParseStringArray(AJSONObject.GetValue('exceptionPath'));
|
|
extension_ := TPolicyManager.ParseStringArray(AJSONObject.GetValue('extension'));
|
|
// 오타(exceptionExtenstion) 대응
|
|
exceptionExtension_ := TPolicyManager.ParseStringArray(AJSONObject.GetValue('exceptionExtenstion'));
|
|
end;
|
|
|
|
function TProcessPolicy.CompareExtension(const AListExtension, AInputExtension: string): Boolean;
|
|
var
|
|
CleanInputExt: string;
|
|
begin
|
|
CleanInputExt := AInputExtension;
|
|
if CleanInputExt.StartsWith('.') then
|
|
CleanInputExt := CleanInputExt.Substring(1);
|
|
Result := SameText(AListExtension, CleanInputExt);
|
|
end;
|
|
|
|
function TProcessPolicy.IsExceptionPath(const APath: string): Boolean;
|
|
var
|
|
ExPath: string;
|
|
begin
|
|
Result := False;
|
|
for ExPath in exceptionPath_ do
|
|
begin
|
|
if AnsiContainsText(APath, ExPath) then
|
|
Exit(True);
|
|
end;
|
|
end;
|
|
|
|
function TProcessPolicy.IsExtension(const AExt: string): Boolean;
|
|
var
|
|
Ext: string;
|
|
begin
|
|
Result := False;
|
|
for Ext in extension_ do
|
|
begin
|
|
if CompareExtension(Ext, AExt) then
|
|
Exit(True);
|
|
end;
|
|
end;
|
|
|
|
function TProcessPolicy.IsExceptionExtension(const AExt: string): Boolean;
|
|
var
|
|
Ext: string;
|
|
begin
|
|
Result := False;
|
|
for Ext in exceptionExtension_ do
|
|
begin
|
|
if CompareExtension(Ext, AExt) then
|
|
Exit(True);
|
|
end;
|
|
end;
|
|
|
|
function TProcessPolicy.HasDeviceType(const ADeviceType: string): Boolean;
|
|
var
|
|
DType: string;
|
|
begin
|
|
Result := False;
|
|
for DType in deviceTypes_ do
|
|
begin
|
|
if SameText(DType, ADeviceType) then
|
|
Exit(True);
|
|
end;
|
|
end;
|
|
|
|
{ TPolicyManager }
|
|
|
|
constructor TPolicyManager.Create(const policyPath: string; const processName: string);
|
|
begin
|
|
inherited Create;
|
|
currentProcessPolicy_ := nil;
|
|
isHookingTarget_ := False;
|
|
policyPath_:= policyPath;
|
|
processName_:= processName;
|
|
deviceControls_ := TDictionary<string, TDeviceControlPolicy>.Create;
|
|
InitializeCriticalSection(cs);
|
|
end;
|
|
|
|
destructor TPolicyManager.Destroy;
|
|
begin
|
|
|
|
EnterCriticalSection(cs);
|
|
try
|
|
currentProcessPolicy_.Free;
|
|
deviceControls_.Free;
|
|
finally
|
|
LeaveCriticalSection(cs);
|
|
DeleteCriticalSection(cs);
|
|
end;
|
|
inherited;
|
|
end;
|
|
|
|
function TPolicyManager.IsExceptionPath(const APath: string): Boolean;
|
|
var
|
|
ExPath: string;
|
|
begin
|
|
EnterCriticalSection(cs);
|
|
try
|
|
Result := False;
|
|
for ExPath in globalExceptionPath_ do
|
|
begin
|
|
if AnsiContainsText(APath, ExPath) then
|
|
Exit(True);
|
|
end;
|
|
|
|
if (currentProcessPolicy_ <> nil) and currentProcessPolicy_.IsExceptionPath(APath) then
|
|
Exit(True);
|
|
finally
|
|
LeaveCriticalSection(cs);
|
|
end;
|
|
end;
|
|
|
|
|
|
class function TPolicyManager.NormalizeProcessName(const AName: string): string;
|
|
begin
|
|
// 1. 확장자 제거 (ChangeFileExt(AName, ''))
|
|
// 2. 대문자로 변환 (.ToUpper)
|
|
Result := ChangeFileExt(ExtractFileName(AName), '').ToUpper;
|
|
end;
|
|
|
|
procedure TPolicyManager.GetPolicy;
|
|
var
|
|
LRootValue, LValue: TJSONValue;
|
|
LJSON, LDataFlowRoot, LPolicyObj, LHookProcessObj, LDeviceControlObj, LProcessObj: TJSONObject;
|
|
LDataFlowKey, LDeviceKey: string;
|
|
LPair: TJSONPair;
|
|
LDevicePolicy: TDeviceControlPolicy;
|
|
begin
|
|
// 초기화
|
|
FreeAndNil(currentProcessPolicy_);
|
|
isHookingTarget_ := False;
|
|
SetLength(globalExceptionPath_, 0);
|
|
deviceControls_.Clear;
|
|
|
|
// 입력된 프로세스 이름을 JSON 키 형식(확장자 없음, 대문자)으로 정규화
|
|
// 예: "Fsquirt.exe" -> "FSQUIRT"
|
|
currentNormalizedName_ := NormalizeProcessName(string(processName_));
|
|
|
|
// LOG('TPolicyManager.GetPolicy, : %s', [currentNormalizedName_]);
|
|
|
|
if not TFile.Exists(policyPath_) then
|
|
begin
|
|
LOG('TPolicyManager.GetPolicy, TFile.Exists Fail : %s', [PChar(policyPath_)]);
|
|
Exit;
|
|
end;
|
|
|
|
LRootValue := TJSONObject.ParseJSONValue(TFile.ReadAllText(string(policyPath_), TEncoding.UTF8));
|
|
if LRootValue = nil then
|
|
begin
|
|
LOG('TPolicyManager.GetPolicy, TJSONObject.ParseJSONValue Fail', []);
|
|
Exit;
|
|
end;
|
|
|
|
try
|
|
if not (LRootValue is TJSONObject) then Exit;
|
|
LJSON := LRootValue as TJSONObject;
|
|
|
|
dataFlowSeq_ := LJSON.GetValue<Integer>('dataFlowSeq');
|
|
LDataFlowRoot := LJSON.GetValue('dataFlow') as TJSONObject;
|
|
if LDataFlowRoot = nil then Exit;
|
|
|
|
LDataFlowKey := dataFlowSeq_.ToString;
|
|
|
|
EnterCriticalSection(cs);
|
|
try
|
|
// 초기화
|
|
FreeAndNil(currentProcessPolicy_);
|
|
isHookingTarget_ := False;
|
|
SetLength(globalExceptionPath_, 0);
|
|
deviceControls_.Clear;
|
|
|
|
LPolicyObj := LDataFlowRoot.GetValue(LDataFlowKey) as TJSONObject;
|
|
if LPolicyObj = nil then Exit;
|
|
|
|
globalExceptionPath_ := ParseStringArray(LPolicyObj.GetValue('exceptionPath'));
|
|
|
|
LDeviceControlObj := LPolicyObj.GetValue('deviceControl') as TJSONObject;
|
|
if LDeviceControlObj <> nil then
|
|
begin
|
|
for LPair in LDeviceControlObj do
|
|
begin
|
|
if LPair.JsonValue is TJSONObject then
|
|
begin
|
|
LDeviceKey := LPair.JsonString.Value;
|
|
LDevicePolicy.policy_ := (LPair.JsonValue as TJSONObject).GetValue<Integer>('policy');
|
|
LDevicePolicy.fileSize_ := (LPair.JsonValue as TJSONObject).GetValue<Integer>('fileSize');
|
|
deviceControls_.Add(LDeviceKey, LDevicePolicy);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
// 3. HookProcess 파싱 및 현재 프로세스 정책 로드
|
|
LHookProcessObj := LPolicyObj.GetValue('hookProcess') as TJSONObject;
|
|
if LHookProcessObj <> nil then
|
|
begin
|
|
// 정규화된 이름("FSQUIRT")으로 바로 조회
|
|
if LHookProcessObj.TryGetValue(currentNormalizedName_, LValue) and (LValue is TJSONObject) then
|
|
begin
|
|
isHookingTarget_ := True;
|
|
LProcessObj := LValue as TJSONObject;
|
|
// 정책 객체 생성
|
|
currentProcessPolicy_ := TProcessPolicy.CreateFromJSON(LProcessObj);
|
|
end;
|
|
end;
|
|
finally
|
|
LeaveCriticalSection(cs);
|
|
end;
|
|
finally
|
|
LRootValue.Free;
|
|
end;
|
|
end;
|
|
|
|
class function TPolicyManager.ParseStringArray(AJSONValue: TJSONValue): TArray<string>;
|
|
var
|
|
LArray: TJSONArray;
|
|
LString: TJSONString;
|
|
I: Integer;
|
|
begin
|
|
Result := [];
|
|
if AJSONValue = nil then Exit;
|
|
|
|
if AJSONValue is TJSONArray then
|
|
begin
|
|
LArray := AJSONValue as TJSONArray;
|
|
SetLength(Result, LArray.Count);
|
|
for I := 0 to LArray.Count - 1 do
|
|
Result[I] := LArray.Items[I].Value;
|
|
end
|
|
else if (AJSONValue is TJSONString) then
|
|
begin
|
|
LString := AJSONValue as TJSONString;
|
|
if not LString.Value.IsEmpty then
|
|
Result := LString.Value.Split(['|']);
|
|
end;
|
|
end;
|
|
|
|
class function TPolicyManager.ParseStringOrArray(AJSONValue: TJSONValue): TArray<string>;
|
|
begin
|
|
Result := ParseStringArray(AJSONValue);
|
|
end;
|
|
|
|
function TPolicyManager.GetDeviceControlPolicy(const ADeviceName: string; out APolicy: TDeviceControlPolicy): Boolean;
|
|
begin
|
|
EnterCriticalSection(cs);
|
|
try
|
|
Result := deviceControls_.TryGetValue(ADeviceName, APolicy);
|
|
finally
|
|
LeaveCriticalSection(cs);
|
|
end;
|
|
end;
|
|
|
|
end.
|