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; exceptionPath_: TArray; extension_: TArray; exceptionExtension_: TArray; 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 read deviceTypes_; property Mode: Integer read mode_; property ExceptionPath: TArray read exceptionPath_; property Extension: TArray read extension_; property ExceptionExtension: TArray read exceptionExtension_; end; TPolicyManager = class private dataFlowSeq_: Integer; globalExceptionPath_: TArray; deviceControls_: TDictionary; currentProcessPolicy_: TProcessPolicy; isHookingTarget_: Boolean; currentNormalizedName_: string; policyPath_: string; processName_: string; cs: TRTLCriticalSection; // 헬퍼 함수들 class function ParseStringArray(AJSONValue: TJSONValue): TArray; class function ParseStringOrArray(AJSONValue: TJSONValue): TArray; 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 read globalExceptionPath_; property DataFlowSeq: Integer read dataFlowSeq_; property DeviceControls: TDictionary 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('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.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('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('policy'); LDevicePolicy.fileSize_ := (LPair.JsonValue as TJSONObject).GetValue('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; 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; 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.