hydrablack

ObfPas Current

Aug 21st, 2025
34
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C 44.09 KB | Help | 0 0
  1. program PasObf;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. uses
  6.   SysUtils, Classes, StrUtils;
  7.  
  8. type
  9.   TTokenKind = (tkIdent, tkString, tkWhitespace, tkComment, tkSymbol, tkNumber, tkOther);
  10.  
  11.   TToken = record
  12.     Kind: TTokenKind;
  13.     Text: string;
  14.     InInterface: Boolean;
  15.     InUses: Boolean;
  16.     InConstBlock: Boolean; // Flag to track if inside a const or resourcestring block
  17.   end;
  18.  
  19.   TTokenArray = array of TToken;
  20.   TStrArray   = array of string;
  21.  
  22.   { Simple case-insensitive string set (sorted TStringList wrapper) }
  23.   TStrSet = class
  24.   private
  25.     S: TStringList;
  26.   public
  27.     constructor Create;
  28.     destructor Destroy; override;
  29.     procedure Add(const v: string);
  30.     function Has(const v: string): Boolean;
  31.     procedure Clear;
  32.   end;
  33.  
  34. var
  35.   // Global variable to hold the randomly generated name for the decryption function
  36.   GXorStrFuncName: string;
  37.   // Global variable to hold the generated polymorphic function code
  38.   GPolymorphicFuncCode: string;
  39.  
  40. constructor TStrSet.Create;
  41. begin
  42.   S := TStringList.Create;
  43.   S.CaseSensitive := False;
  44.   S.Sorted := True;
  45.   S.Duplicates := dupIgnore;
  46. end;
  47.  
  48. destructor TStrSet.Destroy;
  49. begin
  50.   S.Free;
  51.   inherited Destroy;
  52. end;
  53.  
  54. procedure TStrSet.Add(const v: string);
  55. begin
  56.   if v <> '' then S.Add(v);
  57. end;
  58.  
  59. function TStrSet.Has(const v: string): Boolean;
  60. begin
  61.   Result := (v <> '') and (S.IndexOf(v) >= 0);
  62. end;
  63.  
  64. procedure TStrSet.Clear;
  65. begin
  66.   S.Clear;
  67. end;
  68.  
  69. function MakeStrArray(const A: array of string): TStrArray;
  70. var
  71.   i: Integer;
  72. begin
  73.   Result := nil; // Initialize result
  74.   SetLength(Result, Length(A));
  75.   for i := Low(A) to High(A) do
  76.     Result[i] := LowerCase(A[i]);
  77. end;
  78.  
  79. var
  80.   PascalKeywords: TStrArray;
  81.  
  82. procedure InitKeywords;
  83. begin
  84.   // A more comprehensive list of keywords for Delphi/FPC
  85.   PascalKeywords := MakeStrArray([
  86.     'and','array','as','asm','begin','case','class','const','constructor',
  87.     'destructor','div','do','downto','else','end','except','exports','file',
  88.     'finalization','finally','for','function','goto','if','implementation',
  89.     'in','inherited','initialization','inline','interface','is','label','library',
  90.     'mod','nil','not','object','of','or','out','packed','procedure','program',
  91.     'property','raise','record','repeat','resourcestring','set','shl','shr',
  92.     'string','then','threadvar','to','try','type','unit','until','uses',
  93.     'var','while','with','xor','specialize','generic','on','helper','result',
  94.     'true','false','default','abstract','overload','override','reintroduce',
  95.     'virtual','cdecl','stdcall','safecall','register','far','near','private',
  96.     'protected','public','published','pchar','forspecificcpu','foreachcpu',
  97.     'input','PBOOL','integer','PDWORD',
  98.     // extras commonly treated as keywords across Delphi/FPC modes:
  99.     'absolute','external','operator','deprecated','platform','experimental',
  100.     'strict','sealed','final','static','message','pascal','export'
  101.   ]);
  102. end;
  103.  
  104. var
  105.   // CLI options
  106.   GRoot: string = '';
  107.   GInplace: Boolean = False;
  108.   GSeed: LongInt = 0;
  109.   GSeedGiven: Boolean = False;
  110.   GSkipNamesArg: string = '';
  111.   GXorKey: string = ''; // Key for string encryption
  112.  
  113.   // working sets
  114.   PublicNames : TStrSet = nil;
  115.   DeclaredHere: TStrSet = nil;
  116.   SkipNames   : TStrSet = nil;
  117.  
  118. function IsKeyword(const S: string): Boolean;
  119. var
  120.   L: string;
  121.   i: Integer;
  122. begin
  123.   L := LowerCase(S);
  124.   for i := Low(PascalKeywords) to High(PascalKeywords) do
  125.     if L = PascalKeywords[i] then
  126.       Exit(True);
  127.   Result := False;
  128. end;
  129.  
  130. function IsIdentStart(const C: Char): Boolean; inline;
  131. begin
  132.   Result := (C = '_') or (C in ['A'..'Z','a'..'z']);
  133. end;
  134.  
  135. function IsIdentChar(const C: Char): Boolean; inline;
  136. begin
  137.   Result := IsIdentStart(C) or (C in ['0'..'9']);
  138. end;
  139.  
  140. procedure SplitExtPasFiles(const Root: string; List: TStrings; Recursive: Boolean = True);
  141. var
  142.   SR: TSearchRec;
  143.   Sub: string;
  144. begin
  145.   if FindFirst(IncludeTrailingPathDelimiter(Root) + '*', faAnyFile, SR) = 0 then
  146.   try
  147.     repeat
  148.       if (SR.Name = '.') or (SR.Name = '..') then Continue;
  149.       if (SR.Attr and faDirectory) <> 0 then
  150.       begin
  151.         if Recursive then
  152.         begin
  153.           Sub := IncludeTrailingPathDelimiter(Root) + SR.Name;
  154.           SplitExtPasFiles(Sub, List, True);
  155.         end;
  156.       end
  157.       else
  158.       begin
  159.         if AnsiEndsText('.pas', SR.Name) or AnsiEndsText('.pp', SR.Name) then
  160.           List.Add(IncludeTrailingPathDelimiter(Root) + SR.Name);
  161.       end;
  162.     until FindNext(SR) <> 0;
  163.   finally
  164.     FindClose(SR);
  165.   end;
  166. end;
  167.  
  168. procedure AddToken(var Tokens: TTokenArray; var Count: Integer; Kind: TTokenKind;
  169.                    const Text: string; InInterface, InUses, InConstBlock: Boolean);
  170. begin
  171.   if Count >= Length(Tokens) then
  172.     SetLength(Tokens, Length(Tokens) * 2 + 256);
  173.   Tokens[Count].Kind := Kind;
  174.   Tokens[Count].Text := Text;
  175.   Tokens[Count].InInterface := InInterface;
  176.   Tokens[Count].InUses := InUses;
  177.   Tokens[Count].InConstBlock := InConstBlock;
  178.   Inc(Count);
  179. end;
  180.  
  181. procedure LexFile(const Content: string; out Tokens: TTokenArray; out Count: Integer);
  182. var
  183.   i, n: Integer;
  184.   ch: Char;
  185.   buf: string;
  186.   inInterface, inUses, inConstBlock, inResourceStringBlock: Boolean;
  187.   isConstContext: Boolean; // Temporary variable to resolve expression
  188.  
  189.   function Peek(offset: Integer): Char;
  190.   var idx: Integer;
  191.   begin
  192.     idx := i + offset;
  193.     if (idx >= 1) and (idx <= n) then Result := Content[idx] else Result := #0;
  194.   end;
  195.  
  196.   procedure FlushBuf(kind: TTokenKind);
  197.   begin
  198.     if buf <> '' then
  199.     begin
  200.       isConstContext := inConstBlock or inResourceStringBlock;
  201.       AddToken(Tokens, Count, kind, buf, inInterface, inUses, isConstContext);
  202.       buf := '';
  203.     end;
  204.   end;
  205.  
  206.   procedure AddSym(const s: string);
  207.   begin
  208.     FlushBuf(tkOther);
  209.     isConstContext := inConstBlock or inResourceStringBlock;
  210.     AddToken(Tokens, Count, tkSymbol, s, inInterface, inUses, isConstContext);
  211.   end;
  212.  
  213. begin
  214.   Count := 0;
  215.   SetLength(Tokens, 1024);
  216.   i := 1; n := Length(Content);
  217.   buf := '';
  218.   inInterface := False;
  219.   inUses := False;
  220.   inConstBlock := False;
  221.   inResourceStringBlock := False;
  222.   while i <= n do
  223.   begin
  224.     ch := Content[i];
  225.  
  226.     // whitespace
  227.     if ch in [#9, #10, #13, ' '] then
  228.     begin
  229.       FlushBuf(tkOther);
  230.       buf := '';
  231.       repeat
  232.         buf := buf + ch;
  233.         Inc(i);
  234.         if i > n then Break;
  235.         ch := Content[i];
  236.       until not (ch in [#9,#10,#13,' ']);
  237.       isConstContext := inConstBlock or inResourceStringBlock;
  238.       AddToken(Tokens, Count, tkWhitespace, buf, inInterface, inUses, isConstContext);
  239.       buf := '';
  240.       Continue;
  241.     end;
  242.  
  243.     // // comment
  244.     if (ch = '/') and (Peek(1) = '/') then
  245.     begin
  246.       FlushBuf(tkOther);
  247.       buf := '//';
  248.       Inc(i, 2);
  249.       while (i <= n) and not (Content[i] in [#10, #13]) do
  250.       begin
  251.         buf := buf + Content[i];
  252.         Inc(i);
  253.       end;
  254.       isConstContext := inConstBlock or inResourceStringBlock;
  255.       AddToken(Tokens, Count, tkComment, buf, inInterface, inUses, isConstContext);
  256.       buf := '';
  257.       Continue;
  258.     end;
  259.  
  260.     // { } comment
  261.     if ch = '{' then
  262.     begin
  263.       FlushBuf(tkOther);
  264.       buf := '{';
  265.       Inc(i);
  266.       while (i <= n) and (Content[i] <> '}') do
  267.       begin
  268.         buf := buf + Content[i];
  269.         Inc(i);
  270.       end;
  271.       if (i <= n) and (Content[i] = '}') then
  272.       begin
  273.         buf := buf + '}';
  274.         Inc(i);
  275.       end;
  276.       isConstContext := inConstBlock or inResourceStringBlock;
  277.       AddToken(Tokens, Count, tkComment, buf, inInterface, inUses, isConstContext);
  278.       buf := '';
  279.       Continue;
  280.     end;
  281.  
  282.     // (* *) comment
  283.     if (ch = '(') and (Peek(1) = '*') then
  284.     begin
  285.       FlushBuf(tkOther);
  286.       buf := '(*';
  287.       Inc(i, 2);
  288.       while (i <= n) and not ((Content[i] = '*') and (Peek(1) = ')')) do
  289.       begin
  290.         buf := buf + Content[i];
  291.         Inc(i);
  292.       end;
  293.       if (i <= n-1) then
  294.       begin
  295.         buf := buf + '*)';
  296.         Inc(i, 2);
  297.       end;
  298.       isConstContext := inConstBlock or inResourceStringBlock;
  299.       AddToken(Tokens, Count, tkComment, buf, inInterface, inUses, isConstContext);
  300.       buf := '';
  301.       Continue;
  302.     end;
  303.  
  304.     // 'string'
  305.     if ch = '''' then
  306.     begin
  307.       FlushBuf(tkOther);
  308.       buf := '''';
  309.       Inc(i);
  310.       while i <= n do
  311.       begin
  312.         ch := Content[i];
  313.         buf := buf + ch;
  314.         Inc(i);
  315.         if ch = '''' then
  316.         begin
  317.           if (i <= n) and (Content[i] = '''') then
  318.           begin
  319.             // doubled quote inside string; consume it into buffer
  320.             buf := buf + Content[i];
  321.             Inc(i);
  322.             Continue;
  323.           end
  324.           else
  325.             Break;
  326.         end;
  327.       end;
  328.       isConstContext := inConstBlock or inResourceStringBlock;
  329.       AddToken(Tokens, Count, tkString, buf, inInterface, inUses, isConstContext);
  330.       buf := '';
  331.       Continue;
  332.     end;
  333.  
  334.     // identifier
  335.     if IsIdentStart(ch) then
  336.     begin
  337.       FlushBuf(tkOther);
  338.       buf := '';
  339.       while (i <= n) and IsIdentChar(Content[i]) do
  340.       begin
  341.         buf := buf + Content[i];
  342.         Inc(i);
  343.       end;
  344.  
  345.       // section state
  346.       if AnsiSameText(buf, 'interface') then
  347.       begin
  348.         inInterface := True; inUses := False;
  349.       end
  350.       else if AnsiSameText(buf, 'implementation') then
  351.       begin
  352.         inInterface := False; inUses := False; inConstBlock := False; inResourceStringBlock := False;
  353.       end
  354.       else if AnsiSameText(buf, 'uses') then
  355.         inUses := True
  356.       else if AnsiSameText(buf, 'const') then
  357.         inConstBlock := True
  358.       else if AnsiSameText(buf, 'resourcestring') then
  359.         inResourceStringBlock := True
  360.       else if AnsiSameText(buf, 'var') or AnsiSameText(buf, 'type') or AnsiSameText(buf, 'begin') or AnsiSameText(buf, 'procedure') or AnsiSameText(buf, 'function') then
  361.       begin
  362.         inConstBlock := False;
  363.         inResourceStringBlock := False;
  364.       end;
  365.  
  366.       isConstContext := inConstBlock or inResourceStringBlock;
  367.       AddToken(Tokens, Count, tkIdent, buf, inInterface, inUses, isConstContext);
  368.       buf := '';
  369.       Continue;
  370.     end;
  371.  
  372.     // number (supports $hex and decimal with '.')
  373.     if (ch in ['0'..'9']) or ((ch = '$') and (Peek(1) in ['0'..'9','A'..'F','a'..'f'])) then
  374.     begin
  375.       FlushBuf(tkOther);
  376.       buf := '';
  377.       // A bit more robust number parsing to avoid consuming dots from ranges (..)
  378.       if ch = '$' then
  379.       begin
  380.         buf := buf + ch;
  381.         Inc(i);
  382.         while (i <= n) and (Content[i] in ['0'..'9','A'..'F','a'..'f']) do
  383.         begin
  384.           buf := buf + Content[i];
  385.           Inc(i);
  386.         end;
  387.       end else
  388.       begin
  389.         while (i <= n) and (Content[i] in ['0'..'9']) do
  390.         begin
  391.           buf := buf + Content[i];
  392.           Inc(i);
  393.         end;
  394.         if (i <= n) and (Content[i] = '.') and (Peek(1) <> '.') then
  395.         begin
  396.           buf := buf + Content[i];
  397.           Inc(i);
  398.           while (i <= n) and (Content[i] in ['0'..'9']) do
  399.           begin
  400.             buf := buf + Content[i];
  401.             Inc(i);
  402.           end;
  403.         end;
  404.       end;
  405.       isConstContext := inConstBlock or inResourceStringBlock;
  406.       AddToken(Tokens, Count, tkNumber, buf, inInterface, inUses, isConstContext);
  407.       buf := '';
  408.       Continue;
  409.     end;
  410.  
  411.     // punctuation & operators (multi-char first)
  412.     if (ch = '.') and (Peek(1) = '.') then begin AddSym('..'); Inc(i,2); Continue; end;
  413.     if (ch = '<') and (Peek(1) = '=') then begin AddSym('<='); Inc(i,2); Continue; end;
  414.     if (ch = '>') and (Peek(1) = '=') then begin AddSym('>='); Inc(i,2); Continue; end;
  415.     if (ch = '<') and (Peek(1) = '>') then begin AddSym('<>'); Inc(i,2); Continue; end;
  416.     if (ch = ':') and (Peek(1) = '=') then begin AddSym(':='); Inc(i,2); Continue; end;
  417.  
  418.     // default: single char symbol
  419.     if ch in [';', ':', '+','-','*','/','(',')','[',']','{','}','^','@',',','.','<','>','='] then
  420.     begin
  421.       AddSym(ch);
  422.       Inc(i);
  423.       Continue;
  424.     end;
  425.  
  426.     // other unknown char: accumulate as tkOther
  427.     buf := buf + ch;
  428.     Inc(i);
  429.   end;
  430.   FlushBuf(tkOther);
  431.   SetLength(Tokens, Count);
  432. end;
  433.  
  434. function Lower(const s: string): string; inline; begin Result := LowerCase(s); end;
  435.  
  436. function NearestLeftSymbol(const Tokens: TTokenArray; idx: Integer): string;
  437. begin
  438.   Result := '';
  439.   while idx > 0 do
  440.   begin
  441.     Dec(idx);
  442.     if Tokens[idx].Kind in [tkWhitespace, tkComment] then Continue;
  443.     if Tokens[idx].Kind = tkSymbol then Exit(Tokens[idx].Text)
  444.     else Exit('');
  445.   end;
  446. end;
  447.  
  448. function NearestRightSymbol(const Tokens: TTokenArray; idx: Integer): string;
  449. begin
  450.   Result := '';
  451.   while idx < High(Tokens) do
  452.   begin
  453.     Inc(idx);
  454.     if Tokens[idx].Kind in [tkWhitespace, tkComment] then Continue;
  455.     if Tokens[idx].Kind = tkSymbol then Exit(Tokens[idx].Text)
  456.     else Exit('');
  457.   end;
  458. end;
  459.  
  460. function NextNonWsTokenKind(const Tokens: TTokenArray; idx: Integer): TTokenKind;
  461. begin
  462.   while idx < High(Tokens) do
  463.   begin
  464.     Inc(idx);
  465.     if Tokens[idx].Kind in [tkWhitespace, tkComment] then Continue
  466.     else Exit(Tokens[idx].Kind);
  467.   end;
  468.   Result := tkOther;
  469. end;
  470.  
  471. function NextNonWsText(const Tokens: TTokenArray; idx: Integer): string;
  472. begin
  473.   while idx < High(Tokens) do
  474.   begin
  475.     Inc(idx);
  476.     if Tokens[idx].Kind in [tkWhitespace, tkComment] then Continue
  477.     else Exit(Tokens[idx].Text);
  478.   end;
  479.   Result := '';
  480. end;
  481.  
  482. function IsModuleDecl(const Tokens: TTokenArray; idx: Integer): Boolean;
  483. var
  484.   j: Integer;
  485.   lcText: string;
  486. begin
  487.   Result := False;
  488.   j := idx - 1;
  489.   // Skip whitespace and comments between the identifier and the potential keyword.
  490.   while (j >= 0) and (Tokens[j].Kind in [tkWhitespace, tkComment]) do
  491.   begin
  492.     Dec(j);
  493.   end;
  494.  
  495.   if (j >= 0) and (Tokens[j].Kind = tkIdent) then
  496.   begin
  497.     lcText := LowerCase(Tokens[j].Text);
  498.     if (lcText = 'unit') or (lcText = 'program') or (lcText = 'library') then
  499.       Exit(True);
  500.   end;
  501.  
  502.   if (j >= 0) and (Tokens[j].Kind = tkSymbol) and (Tokens[j].Text = '.') then
  503.   begin
  504.     Dec(j);
  505.     while (j >= 0) and (Tokens[j].Kind in [tkWhitespace, tkComment]) do Dec(j);
  506.     if (j >= 0) and (Tokens[j].Kind = tkIdent) then
  507.     begin
  508.         lcText := LowerCase(Tokens[j].Text);
  509.         if (lcText = 'unit') or (lcText = 'program') or (lcText = 'library') then
  510.           Exit(True);
  511.     end;
  512.   end;
  513. end;
  514.  
  515. function IsUnitNameInUses(const Tokens: TTokenArray; idx: Integer): Boolean; inline;
  516. begin
  517.   Result := Tokens[idx].InUses;
  518. end;
  519.  
  520. function IsTypeDeclName(const Tokens: TTokenArray; idx: Integer): Boolean;
  521. var
  522.   rightSym, nxtText: string;
  523.   nxtKind: TTokenKind;
  524. begin
  525.   Result := False;
  526.   rightSym := NearestRightSymbol(Tokens, idx);
  527.   if rightSym <> '=' then Exit(False);
  528.   nxtKind := NextNonWsTokenKind(Tokens, idx);
  529.   nxtText := LowerCase(NextNonWsText(Tokens, idx));
  530.   if (nxtKind = tkSymbol) and (nxtText = '(') then Exit(True);
  531.   if (nxtKind = tkIdent) and ((nxtText = 'record') or (nxtText = 'class') or
  532.                               (nxtText = 'object') or (nxtText = 'set') or
  533.                               (nxtText = 'interface')) then
  534.     Exit(True);
  535. end;
  536.  
  537. function IsDeclName(const Tokens: TTokenArray; idx: Integer): Boolean;
  538. var
  539.   j: Integer;
  540.   prevWord, lcText: string;
  541. begin
  542.   Result := False;
  543.   if Assigned(PublicNames) and PublicNames.Has(Tokens[idx].Text) then
  544.       Exit(False);
  545.  
  546.   if IsKeyword(Tokens[idx].Text) then Exit(False);
  547.   if IsModuleDecl(Tokens, idx) then Exit(False);
  548.   if IsUnitNameInUses(Tokens, idx) then Exit(False);
  549.  
  550.   // Check for `name: type` or `name = value` patterns in declarations
  551.   if (NearestRightSymbol(Tokens, idx) = ':') or
  552.      ((NearestRightSymbol(Tokens, idx) = '=') and not IsTypeDeclName(Tokens, idx)) then
  553.   begin
  554.     // It's a declaration. Let's find the keyword that started this block.
  555.     j := idx - 1;
  556.     while j >= 0 do
  557.     begin
  558.       if Tokens[j].Kind = tkIdent then
  559.       begin
  560.         prevWord := LowerCase(Tokens[j].Text);
  561.         if (prevWord = 'var') or (prevWord = 'const') or (prevWord = 'resourcestring') or
  562.            (prevWord = 'threadvar') or (prevWord = 'type') or
  563.            (prevWord = 'function') or (prevWord = 'procedure') or
  564.            (prevWord = 'property') then
  565.         begin
  566.           Exit(True);
  567.         end;
  568.       end;
  569.       // Stop searching if we hit a block-ending symbol like a semicolon or another begin/end
  570.       if (Tokens[j].Kind = tkSymbol) and (Tokens[j].Text = ';') then Break;
  571.       if (Tokens[j].Kind = tkIdent) then
  572.       begin
  573.         lcText := LowerCase(Tokens[j].Text);
  574.         if (lcText = 'begin') or (lcText = 'end') or (lcText = 'implementation') then Break;
  575.       end;
  576.       Dec(j);
  577.     end;
  578.   end;
  579.  
  580.   Result := False;
  581. end;
  582.  
  583. function IsProtectedNameEverywhere(const id: string): Boolean; inline;
  584. begin
  585.   Result := (Assigned(PublicNames) and PublicNames.Has(id))
  586.          or (Assigned(SkipNames) and SkipNames.Has(id));
  587. end;
  588.  
  589. function IsRenameCandidateUse(const Tokens: TTokenArray; idx: Integer): Boolean;
  590. var
  591.   id: string;
  592. begin
  593.   id := Tokens[idx].Text;
  594.   if id = '' then Exit(False);
  595.   if not Assigned(DeclaredHere) then Exit(False);
  596.   if not DeclaredHere.Has(id) then Exit(False);
  597.   if IsKeyword(id) then Exit(False);
  598.   if IsModuleDecl(Tokens, idx) then Exit(False);
  599.   if IsUnitNameInUses(Tokens, idx) then Exit(False);
  600.   if IsProtectedNameEverywhere(id) then Exit(False);
  601.   Result := True;
  602. end;
  603.  
  604. function RandAlphaNum: Char;
  605. const
  606.   Chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
  607. begin
  608.   Result := Chars[Random(Length(Chars)) + 1];
  609. end;
  610.  
  611. function GenName(Len: Integer): string;
  612. var
  613.   i: Integer;
  614. begin
  615.   if Len < 1 then Len := 8;
  616.   Result := Chr(Ord('a') + Random(26));
  617.   for i := 2 to Len do
  618.     Result := Result + RandAlphaNum;
  619. end;
  620.  
  621. function NextObfName(const Seen: TStrings): string;
  622. begin
  623.   repeat
  624.     Result := GenName(10);
  625.   until Seen.IndexOf(Result) < 0;
  626.   Seen.Add(Result);
  627. end;
  628.  
  629. procedure InitSkipList;
  630. const
  631.   HardSkips: array[0..5] of string = (
  632.     'SetDefaultDllDirectoriesNT', 'AddDllDirectoryNT', 'RemoveDllDirectoryNT',
  633.     'SetDefaultDllDirectories', 'AddDllDirectory', 'RemoveDllDirectory'
  634.   );
  635.   BuiltInFuncs: array[0..58] of string = (
  636.     'Abs', 'Addr', 'Append', 'ArcTan', 'Assign', 'BlockRead', 'BlockWrite',
  637.     'Break', 'Chr', 'Close', 'Concat', 'Continue', 'Copy', 'Cos', 'Dec',
  638.     'Delete', 'Dispose', 'Eof', 'Eoln', 'Erase', 'Exit', 'Exp', 'FilePos',
  639.     'FileSize', 'FillChar', 'Flush', 'Frac', 'FreeMem', 'GetMem', 'Halt',
  640.     'Hi', 'High', 'Inc', 'Insert', 'Int', 'IOResult', 'Length', 'Ln', 'Lo',
  641.     'Low', 'Max', 'Min', 'Move', 'New', 'Odd', 'Ord', 'Pi', 'Pos', 'Pred',
  642.     'Ptr', 'Random', 'Randomize', 'Read', 'ReadLn', 'ReWrite', 'Rename',
  643.     'Reset', 'Round', 'RunError'
  644.   );
  645.   BuiltInFuncs2: array[0..19] of string = (
  646.     'Seek', 'SeekEof', 'SeekEoln', 'SetLength', 'SetTextBuf', 'Sin', 'SizeOf',
  647.     'Sqr', 'Sqrt', 'Str', 'Succ', 'Trunc', 'UpCase', 'Val', 'Write', 'WriteLn',
  648.     'TObject', 'Self', 'Result', 'inherited'
  649.   );
  650.   PascalTypes: array[0..27] of string = (
  651.     'Integer', 'LongInt', 'ShortInt', 'Byte', 'Word', 'Cardinal', 'SmallInt',
  652.     'Real', 'Single', 'Double', 'Extended', 'Comp', 'Currency', 'Boolean',
  653.     'LongBool', 'Char', 'String', 'PChar', 'PPChar', 'Pointer', 'AnsiString',
  654.     'WideString', 'Int64', 'QWord', 'THandle', 'TFarProc', 'PtrInt', 'PtrUInt'
  655.   );
  656.   WinApiTypes: array[0..40] of string = (
  657.     'HANDLE', 'HWND', 'DWORD', 'WORD', 'BOOL', 'LPSTR', 'LPCSTR', 'LPWSTR',
  658.     'LPCWSTR', 'HMODULE', 'HINSTANCE', 'HRGN', 'HDC', 'HGDIOBJ', 'HICON',
  659.     'HBRUSH', 'HPEN', 'HFONT', 'HBITMAP', 'HMENU', 'LONG', 'UINT', 'LPARAM',
  660.     'WPARAM', 'LRESULT', 'LPVOID', 'LPCVOID', 'PBYTE', 'DWORD_PTR',
  661.     'ULONG_PTR', 'SIZE_T', 'TImageInfo', 'TImageData', 'TPoint', 'TRect',
  662.     'TLogFontA', 'TLogFontW', 'TLogFont', 'TIconInfo', 'TBitmap', 'TMessage'
  663.   );
  664.   CInterOpTypes: array[0..18] of string = (
  665.     'cint', 'cuint', 'cchar', 'cschar', 'cuchar', 'cshort', 'cushort',
  666.     'clong', 'culong', 'clonglong', 'culonglong', 'cfloat', 'cdouble',
  667.     'pcchar', 'pccchar', 'cint64', 'cuint64', 'cpointer', 'c UCS2char'
  668.   );
  669.   ElfTypes: array[0..11] of string = (
  670.     'TElfIdent', 'TElf32Hdr', 'TElf64Hdr', 'TElf32SectHdr', 'TElf64SectHdr',
  671.     'TElf32Symbol', 'TElf64Symbol', 'PElf32Hdr', 'PElf64Hdr', 'PElf32SectHdr',
  672.     'PElf64SectHdr', 'PElf32Symbol'
  673.   );
  674.  
  675. var
  676.   item: string;
  677. begin
  678.   if not Assigned(SkipNames) then Exit;
  679.  
  680.   for item in HardSkips do SkipNames.Add(item);
  681.   for item in BuiltInFuncs do SkipNames.Add(item);
  682.   for item in BuiltInFuncs2 do SkipNames.Add(item);
  683.   for item in PascalTypes do SkipNames.Add(item);
  684.   for item in WinApiTypes do SkipNames.Add(item);
  685.   for item in CInterOpTypes do SkipNames.Add(item);
  686.   for item in ElfTypes do SkipNames.Add(item);
  687. end;
  688.  
  689. procedure CollectIdentifierLikeStrings(const Files: TStrings);
  690. var
  691.   f, i, Count: Integer;
  692.   FS: TFileStream;
  693.   SS: TStringStream;
  694.   Content, s: string;
  695.   Tokens: TTokenArray;
  696.   ok: Boolean;
  697.   k: Integer;
  698. begin
  699.   Tokens := nil;
  700.   if not Assigned(SkipNames) then Exit;
  701.   for f := 0 to Files.Count - 1 do
  702.   begin
  703.     FS := TFileStream.Create(Files[f], fmOpenRead or fmShareDenyNone);
  704.     try
  705.       SS := TStringStream.Create('');
  706.       try
  707.         SS.CopyFrom(FS, FS.Size);
  708.         Content := SS.DataString;
  709.       finally
  710.         SS.Free;
  711.       end;
  712.     finally
  713.       FS.Free;
  714.     end;
  715.  
  716.     LexFile(Content, Tokens, Count);
  717.  
  718.     for i := 0 to Count - 1 do
  719.       if (Tokens[i].Kind = tkString) then
  720.       begin
  721.         s := Tokens[i].Text;
  722.         if (Length(s) >= 2) and (s[1] = '''') and (s[Length(s)] = '''') then
  723.         begin
  724.           s := StringReplace(Copy(s, 2, Length(s)-2), '''''', '''', [rfReplaceAll]);
  725.           ok := (Length(s) > 0) and IsIdentStart(s[1]);
  726.           if ok then
  727.             for k := 2 to Length(s) do
  728.               if not IsIdentChar(s[k]) then
  729.               begin
  730.                 ok := False;
  731.                 Break;
  732.               end;
  733.           if ok then
  734.             SkipNames.Add(s);
  735.         end;
  736.       end;
  737.   end;
  738. end;
  739.  
  740. procedure CollectWrapperVarsFromGetProcAddress(const Files: TStrings);
  741. var
  742.   f, i, n, j: Integer;
  743.   FS: TFileStream;
  744.   SS: TStringStream;
  745.   Content: string;
  746.   Tokens: TTokenArray;
  747.   lhs: string;
  748. begin
  749.   Tokens := nil;
  750.   if not Assigned(SkipNames) then Exit;
  751.   for f := 0 to Files.Count - 1 do
  752.   begin
  753.     FS := TFileStream.Create(Files[f], fmOpenRead or fmShareDenyNone);
  754.     try
  755.       SS := TStringStream.Create('');
  756.       try
  757.         SS.CopyFrom(FS, FS.Size);
  758.         Content := SS.DataString;
  759.       finally
  760.         SS.Free;
  761.       end;
  762.     finally
  763.       FS.Free;
  764.     end;
  765.  
  766.     LexFile(Content, Tokens, n);
  767.  
  768.     i := 0;
  769.     while i < n do
  770.     begin
  771.       // Pattern: varName := GetProcAddress(...)
  772.       if (Tokens[i].Kind = tkIdent) then
  773.       begin
  774.         lhs := Tokens[i].Text;
  775.         j := i + 1;
  776.         while (j < n) and (Tokens[j].Kind in [tkWhitespace, tkComment]) do Inc(j);
  777.         if (j + 1 < n) and (Tokens[j].Kind = tkSymbol) and (Tokens[j].Text = ':=') then
  778.         begin
  779.           j := j + 1;
  780.           while (j < n) and (Tokens[j].Kind in [tkWhitespace, tkComment]) do Inc(j);
  781.           if (j < n) and (Tokens[j].Kind = tkIdent) and AnsiSameText(Tokens[j].Text, 'GetProcAddress') then
  782.           begin
  783.             SkipNames.Add(lhs);
  784.             i := j; // Skip forward
  785.             Continue;
  786.           end;
  787.         end;
  788.       end;
  789.       Inc(i);
  790.     end;
  791.   end;
  792. end;
  793.  
  794. procedure CollectTypeNames(const Files: TStrings);
  795. var
  796.   f, i, Count: Integer;
  797.   FS: TFileStream;
  798.   SS: TStringStream;
  799.   Content: string;
  800.   Toks: TTokenArray;
  801.   rightSym: string;
  802.   j: Integer;
  803. begin
  804.   Toks := nil;
  805.   if not Assigned(SkipNames) then Exit;
  806.   for f := 0 to Files.Count - 1 do
  807.   begin
  808.     FS := TFileStream.Create(Files[f], fmOpenRead or fmShareDenyNone);
  809.     try
  810.       SS := TStringStream.Create('');
  811.       try
  812.         SS.CopyFrom(FS, FS.Size);
  813.         Content := SS.DataString;
  814.       finally
  815.         SS.Free;
  816.       end;
  817.     finally
  818.       FS.Free;
  819.     end;
  820.  
  821.     LexFile(Content, Toks, Count);
  822.  
  823.     for i := 1 to Count - 1 do
  824.       if (Toks[i].Kind = tkIdent) then
  825.       begin
  826.         // Find previous non-whitespace token
  827.         j := i - 1;
  828.         while (j > 0) and (Toks[j].Kind in [tkWhitespace, tkComment]) do Dec(j);
  829.  
  830.         if (Toks[j].Kind = tkIdent) and (LowerCase(Toks[j].Text) = 'type') then
  831.         begin
  832.           rightSym := NearestRightSymbol(Toks, i);
  833.           if rightSym = '=' then
  834.             SkipNames.Add(Toks[i].Text);
  835.         end;
  836.       end;
  837.   end;
  838. end;
  839.  
  840. procedure CollectAllPublicNames(const Files: TStrings);
  841. var
  842.   f, i, Count, j: Integer;
  843.   FS: TFileStream;
  844.   SS: TStringStream;
  845.   Content: string;
  846.   Tokens: TTokenArray;
  847.   lcText: string;
  848. begin
  849.   Tokens := nil;
  850.   if not Assigned(PublicNames) then Exit;
  851.   PublicNames.Clear;
  852.  
  853.   for f := 0 to Files.Count - 1 do
  854.   begin
  855.     FS := TFileStream.Create(Files[f], fmOpenRead or fmShareDenyNone);
  856.     try
  857.       SS := TStringStream.Create('');
  858.       try
  859.         SS.CopyFrom(FS, FS.Size);
  860.         Content := SS.DataString;
  861.       finally
  862.         SS.Free;
  863.       end;
  864.     finally
  865.       FS.Free;
  866.     end;
  867.  
  868.     LexFile(Content, Tokens, Count);
  869.  
  870.     for i := 0 to Count - 1 do
  871.     begin
  872.       if not Tokens[i].InInterface then Continue;
  873.       if Tokens[i].Kind <> tkIdent then Continue;
  874.  
  875.       // Simple heuristic: if an identifier in the interface section is part of
  876.       // a declaration (followed by : or =), it's public.
  877.       // This is broad but safer than complex parsing.
  878.       j := i + 1;
  879.       while (j < Count) and (Tokens[j].Kind in [tkWhitespace, tkComment]) do Inc(j);
  880.       if j < Count then
  881.       begin
  882.         if (Tokens[j].Kind = tkSymbol) and ((Tokens[j].Text = ':') or (Tokens[j].Text = '=')) then
  883.           PublicNames.Add(Tokens[i].Text);
  884.       end;
  885.  
  886.       // Also consider procedure/function names public
  887.       j := i - 1;
  888.       while (j >= 0) and (Tokens[j].Kind in [tkWhitespace, tkComment]) do Dec(j);
  889.       if j >= 0 then
  890.       begin
  891.         if (Tokens[j].Kind = tkIdent) then
  892.         begin
  893.           lcText := LowerCase(Tokens[j].Text);
  894.           if (lcText = 'procedure') or (lcText = 'function') then
  895.             PublicNames.Add(Tokens[i].Text);
  896.         end;
  897.       end;
  898.     end;
  899.   end;
  900. end;
  901.  
  902. procedure BuildDeclaredHere(const Tokens: TTokenArray; DeclaredHere: TStrSet);
  903. var
  904.   i: Integer;
  905. begin
  906.   if DeclaredHere<>nil then DeclaredHere.Clear;
  907.   for i := 0 to High(Tokens) do
  908.   begin
  909.     if (Tokens[i].Kind = tkIdent) and IsDeclName(Tokens, i) then
  910.       DeclaredHere.Add(Tokens[i].Text);
  911.   end;
  912. end;
  913.  
  914. procedure CollectDeclaredHere(const Files: TStrings);
  915. var
  916.   f, i, Count: Integer;
  917.   FS: TFileStream;
  918.   SS: TStringStream;
  919.   Content: string;
  920.   Tokens: TTokenArray;
  921. begin
  922.   Tokens := nil;
  923.   if not Assigned(DeclaredHere) then Exit;
  924.   DeclaredHere.Clear;
  925.  
  926.   for f := 0 to Files.Count - 1 do
  927.   begin
  928.     FS := TFileStream.Create(Files[f], fmOpenRead or fmShareDenyNone);
  929.     try
  930.       SS := TStringStream.Create('');
  931.       try
  932.         SS.CopyFrom(FS, FS.Size);
  933.         Content := SS.DataString;
  934.       finally
  935.         SS.Free;
  936.       end;
  937.     finally
  938.       FS.Free;
  939.     end;
  940.  
  941.     LexFile(Content, Tokens, Count);
  942.  
  943.     for i := 0 to Count - 1 do
  944.       if (Tokens[i].Kind = tkIdent) and IsDeclName(Tokens, i) then
  945.         DeclaredHere.Add(Tokens[i].Text);
  946.   end;
  947. end;
  948.  
  949. procedure BuildMap(const Files: TStrings; MapOrig, MapNew: TStrings);
  950. var
  951.   f, i, Count: Integer;
  952.   FS: TFileStream;
  953.   SS: TStringStream;
  954.   Content: string;
  955.   Tokens: TTokenArray;
  956.   NewNames: TStringList;
  957.   LowerToIdx: TStringList;
  958.   idLower, newName: string;
  959. begin
  960.   Tokens := nil;
  961.   NewNames := TStringList.Create;
  962.   LowerToIdx := TStringList.Create;
  963.   try
  964.     LowerToIdx.CaseSensitive := False;
  965.     LowerToIdx.Sorted := True;
  966.     LowerToIdx.Duplicates := dupIgnore;
  967.  
  968.     for f := 0 to Files.Count - 1 do
  969.     begin
  970.       FS := TFileStream.Create(Files[f], fmOpenRead or fmShareDenyNone);
  971.       try
  972.         SS := TStringStream.Create('');
  973.         try
  974.           SS.CopyFrom(FS, FS.Size);
  975.           Content := SS.DataString;
  976.         finally
  977.           SS.Free;
  978.         end;
  979.       finally
  980.         FS.Free;
  981.       end;
  982.  
  983.       LexFile(Content, Tokens, Count);
  984.  
  985.       for i := 0 to Count - 1 do
  986.         if (Tokens[i].Kind = tkIdent)
  987.            and not IsKeyword(Tokens[i].Text)
  988.            and not IsModuleDecl(Tokens, i)
  989.            and not IsUnitNameInUses(Tokens, i)
  990.            and not IsProtectedNameEverywhere(Tokens[i].Text) then
  991.         begin
  992.           idLower := Lower(Tokens[i].Text);
  993.           if LowerToIdx.IndexOf(idLower) < 0 then
  994.             LowerToIdx.Add(idLower);
  995.         end;
  996.     end;
  997.  
  998.     for i := 0 to LowerToIdx.Count - 1 do
  999.     begin
  1000.       newName := NextObfName(NewNames);
  1001.       MapOrig.Add(LowerToIdx[i]);
  1002.       MapNew.Add(newName);
  1003.     end;
  1004.   finally
  1005.     NewNames.Free;
  1006.     LowerToIdx.Free;
  1007.   end;
  1008. end;
  1009.  
  1010. function LookupNewName(const OrigLower: string; MapOrig, MapNew: TStrings; out NewName: string): Boolean;
  1011. var
  1012.   i: Integer;
  1013. begin
  1014.   i := MapOrig.IndexOf(OrigLower);
  1015.   Result := (i >= 0);
  1016.   if Result then NewName := MapNew[i];
  1017. end;
  1018.  
  1019. function EncryptStringForPascal(const s: string; const Key: string): string;
  1020. var
  1021.   i: Integer;
  1022.   encryptedByte: Byte;
  1023. begin
  1024.   if s = '' then Exit('[]'); // Return empty byte array for empty string
  1025.   if Key = '' then
  1026.   begin
  1027.     // No key, just format as byte array
  1028.     Result := '[';
  1029.     for i := 1 to Length(s) do
  1030.     begin
  1031.       Result := Result + IntToStr(Ord(s[i]));
  1032.       if i < Length(s) then Result := Result + ', ';
  1033.     end;
  1034.     Result := Result + ']';
  1035.     Exit;
  1036.   end;
  1037.  
  1038.   Result := '[';
  1039.   for i := 1 to Length(s) do
  1040.   begin
  1041.     encryptedByte := Ord(s[i]) xor Ord(Key[((i-1) mod Length(Key)) + 1]);
  1042.     Result := Result + IntToStr(encryptedByte);
  1043.     if i < Length(s) then Result := Result + ', ';
  1044.   end;
  1045.   Result := Result + ']';
  1046. end;
  1047.  
  1048. function IsCaseLabel(const Tokens: TTokenArray; idx: Integer): Boolean;
  1049. var
  1050.   j: Integer;
  1051. begin
  1052.   Result := False;
  1053.   if idx >= High(Tokens) then Exit(False);
  1054.   j := idx + 1;
  1055.   while j <= High(Tokens) do
  1056.   begin
  1057.     if Tokens[j].Kind in [tkWhitespace, tkComment] then
  1058.     begin
  1059.       Inc(j);
  1060.       continue;
  1061.     end;
  1062.  
  1063.     if (Tokens[j].Kind = tkSymbol) and (Tokens[j].Text = ':') then
  1064.       Result := True;
  1065.  
  1066.     break;
  1067.   end;
  1068. end;
  1069.  
  1070. function IsGUID(const s: string): Boolean;
  1071. begin
  1072.   Result := (Length(s) > 2) and (s[1] = '{') and (s[Length(s)] = '}');
  1073. end;
  1074.  
  1075. procedure RewriteFile(const InPath: string; const OutPath: string;
  1076.                       MapOrig, MapNew: TStrings);
  1077. var
  1078.   Content: string;
  1079.   FS: TFileStream;
  1080.   SS: TStringStream;
  1081.   Tokens: TTokenArray;
  1082.   Count, i, j, parenIdx, implementationLine, injectionPoint: Integer;
  1083.   outBuf, repl, rawString, actualString, prevIdent, lcText, lineText: string;
  1084.   needsPChar, needsPWideChar, isExternalDecl, stringWasEncrypted: Boolean;
  1085.   SL: TStringList;
  1086. begin
  1087.   FS := TFileStream.Create(InPath, fmOpenRead or fmShareDenyNone);
  1088.   try
  1089.     SS := TStringStream.Create('');
  1090.     try
  1091.       SS.CopyFrom(FS, FS.Size);
  1092.       Content := SS.DataString;
  1093.     finally
  1094.       SS.Free;
  1095.     end;
  1096.   finally
  1097.     FS.Free;
  1098.   end;
  1099.  
  1100.   LexFile(Content, Tokens, Count);
  1101.   BuildDeclaredHere(Tokens, DeclaredHere);
  1102.  
  1103.   outBuf := '';
  1104.   stringWasEncrypted := False;
  1105.  
  1106.   for i := 0 to Count - 1 do
  1107.   begin
  1108.     case Tokens[i].Kind of
  1109.       tkIdent:
  1110.       begin
  1111.         repl := Tokens[i].Text;
  1112.         if IsRenameCandidateUse(Tokens, i) then
  1113.           if LookupNewName(Lower(Tokens[i].Text), MapOrig, MapNew, repl) then
  1114.             ; // repl set
  1115.         outBuf := outBuf + repl;
  1116.       end;
  1117.       tkString:
  1118.       begin
  1119.         rawString := Tokens[i].Text;
  1120.  
  1121.         if rawString = '''%%XOR_KEY%%''' then
  1122.         begin
  1123.           outBuf := outBuf + '''' + StringReplace(GXorKey, '''', '''''', [rfReplaceAll]) + '''';
  1124.         end
  1125.         else if (Length(rawString) > 2) and (rawString[1] = '''') and (not Tokens[i].InConstBlock) then
  1126.         begin
  1127.           j := i - 1;
  1128.           isExternalDecl := False;
  1129.           while j >= 0 do
  1130.           begin
  1131.             if Tokens[j].Kind in [tkWhitespace, tkComment] then
  1132.             begin
  1133.               Dec(j);
  1134.               continue;
  1135.             end;
  1136.             if Tokens[j].Kind = tkIdent then
  1137.             begin
  1138.               lcText := LowerCase(Tokens[j].Text);
  1139.               if (lcText = 'external') or (lcText = 'name') then
  1140.                 isExternalDecl := True;
  1141.             end;
  1142.             break;
  1143.           end;
  1144.  
  1145.           if isExternalDecl then
  1146.           begin
  1147.             outBuf := outBuf + Tokens[i].Text;
  1148.             continue;
  1149.           end;
  1150.  
  1151.           actualString := StringReplace(Copy(rawString, 2, Length(rawString) - 2), '''''', '''', [rfReplaceAll]);
  1152.  
  1153.           if (Length(actualString) <= 1) or IsCaseLabel(Tokens, i) or IsGUID(actualString) then
  1154.           begin
  1155.             outBuf := outBuf + Tokens[i].Text;
  1156.           end
  1157.           else
  1158.           begin
  1159.             stringWasEncrypted := True;
  1160.  
  1161.             prevIdent := '';
  1162.             parenIdx := -1;
  1163.             for j := i-1 downto 0 do
  1164.             begin
  1165.               if Tokens[j].Kind = tkSymbol then
  1166.               begin
  1167.                 if Tokens[j].Text = '(' then
  1168.                 begin
  1169.                   parenIdx := j;
  1170.                   break;
  1171.                 end;
  1172.                 if (Tokens[j].Text = ';') or (Tokens[j].Text = ':=') or (Tokens[j].Text = '=') then break;
  1173.               end;
  1174.             end;
  1175.  
  1176.             if parenIdx > 0 then
  1177.             begin
  1178.               for j := parenIdx - 1 downto 0 do
  1179.               begin
  1180.                 if Tokens[j].Kind in [tkWhitespace, tkComment] then continue;
  1181.                 if Tokens[j].Kind = tkIdent then
  1182.                 begin
  1183.                   prevIdent := Lower(Tokens[j].Text);
  1184.                 end;
  1185.                 break;
  1186.               end;
  1187.             end;
  1188.  
  1189.             needsPChar := (prevIdent = 'loadlibrary') or (prevIdent = 'getprocaddress') or (prevIdent = 'getmodulehandle') or (prevIdent = 'outputdebugstringa') or (prevIdent = 'outputdebugstring');
  1190.             needsPWideChar := (Length(prevIdent) > 1) and (LowerCase(prevIdent[Length(prevIdent)]) = 'w');
  1191.  
  1192.             if needsPWideChar then
  1193.                 outBuf := outBuf + 'PWideChar(WideString(' + GXorStrFuncName + '(' + EncryptStringForPascal(actualString, GXorKey) + ')))'
  1194.             else if needsPChar then
  1195.                 outBuf := outBuf + 'PAnsiChar(' + GXorStrFuncName + '(' + EncryptStringForPascal(actualString, GXorKey) + '))'
  1196.             else
  1197.                 outBuf := outBuf + GXorStrFuncName + '(' + EncryptStringForPascal(actualString, GXorKey) + ')';
  1198.           end;
  1199.         end
  1200.         else
  1201.         begin
  1202.           outBuf := outBuf + Tokens[i].Text;
  1203.         end;
  1204.       end;
  1205.     else
  1206.       outBuf := outBuf + Tokens[i].Text;
  1207.     end;
  1208.   end;
  1209.  
  1210.   if stringWasEncrypted then
  1211.   begin
  1212.     SL := TStringList.Create;
  1213.     try
  1214.       SL.Text := outBuf;
  1215.       implementationLine := -1;
  1216.       for i := 0 to SL.Count - 1 do
  1217.       begin
  1218.         if Trim(LowerCase(SL[i])) = 'implementation' then
  1219.         begin
  1220.           implementationLine := i;
  1221.           break;
  1222.         end;
  1223.       end;
  1224.  
  1225.       injectionPoint := -1;
  1226.  
  1227.       if implementationLine > -1 then // It's a Unit file
  1228.       begin
  1229.         injectionPoint := implementationLine + 1; // Default to line after 'implementation'
  1230.         for i := implementationLine + 1 to SL.Count - 1 do
  1231.         begin
  1232.           lineText := Trim(LowerCase(SL[i]));
  1233.           if lineText = '' then continue;
  1234.           if StartsText('{', lineText) or StartsText('//', lineText) or StartsText('(*', lineText) then continue;
  1235.  
  1236.           if StartsText('uses', lineText) then
  1237.           begin
  1238.             for j := i to SL.Count - 1 do
  1239.             begin
  1240.               if Pos(';', SL[j]) > 0 then
  1241.               begin
  1242.                 injectionPoint := j + 1;
  1243.                 break;
  1244.               end;
  1245.             end;
  1246.           end
  1247.           else
  1248.           begin
  1249.              injectionPoint := i;
  1250.           end;
  1251.           break;
  1252.         end;
  1253.       end
  1254.       else // It's a Program file (no 'implementation' section)
  1255.       begin
  1256.         // Find the last declaration block before 'begin'
  1257.         for i := 0 to SL.Count - 1 do
  1258.         begin
  1259.           lineText := Trim(LowerCase(SL[i]));
  1260.           if (lineText = 'var') or (lineText = 'const') or (lineText = 'type') or (lineText = 'begin') then
  1261.           begin
  1262.             injectionPoint := i;
  1263.             break;
  1264.           end;
  1265.         end;
  1266.       end;
  1267.  
  1268.       if injectionPoint > -1 then
  1269.       begin
  1270.         SL.Insert(injectionPoint, GPolymorphicFuncCode + #13#10);
  1271.         outBuf := SL.Text;
  1272.         WriteLn('Injector: Polymorphic runtime function injected into ', ExtractFileName(InPath));
  1273.       end;
  1274.     finally
  1275.       SL.Free;
  1276.     end;
  1277.   end;
  1278.  
  1279.   with TStringStream.Create(outBuf) do
  1280.   try
  1281.     SaveToFile(OutPath);
  1282.   finally
  1283.     Free;
  1284.   end;
  1285. end;
  1286.  
  1287.  
  1288. procedure RewriteAll(const Files: TStrings; MapOrig, MapNew: TStrings; Inplace: Boolean);
  1289. var
  1290.   f: Integer;
  1291.   outPath, bakPath: string;
  1292. begin
  1293.   for f := 0 to Files.Count - 1 do
  1294.   begin
  1295.     if Inplace then
  1296.     begin
  1297.       outPath := Files[f];
  1298.       bakPath := outPath + '.bak';
  1299.       if FileExists(bakPath) then DeleteFile(bakPath);
  1300.       if not RenameFile(outPath, bakPath) then
  1301.         raise Exception.Create('Failed to backup ' + outPath);
  1302.       try
  1303.         WriteLn('Rewriting: ', ExtractFileName(outPath));
  1304.         RewriteFile(bakPath, outPath, MapOrig, MapNew);
  1305.         DeleteFile(bakPath);
  1306.       except
  1307.         on E: Exception do
  1308.         begin
  1309.           WriteLn('ERROR rewriting ', ExtractFileName(outPath), ': ', E.Message);
  1310.           if FileExists(bakPath) then
  1311.           begin
  1312.             if FileExists(outPath) then DeleteFile(outPath);
  1313.             RenameFile(bakPath, outPath); // Restore backup on failure
  1314.           end;
  1315.           raise;
  1316.         end;
  1317.       end;
  1318.     end
  1319.     else
  1320.     begin
  1321.       outPath := ChangeFileExt(Files[f], '.obf' + ExtractFileExt(Files[f]));
  1322.       WriteLn('Rewriting ', ExtractFileName(Files[f]), ' to ', ExtractFileName(outPath));
  1323.       RewriteFile(Files[f], outPath, MapOrig, MapNew);
  1324.     end;
  1325.   end;
  1326. end;
  1327.  
  1328. procedure SaveMapCsv(const Root: string; MapOrig, MapNew: TStrings);
  1329. var
  1330.   i: Integer;
  1331.   S: TStringList;
  1332. begin
  1333.   S := TStringList.Create;
  1334.   try
  1335.     S.Add('original_lower,new_name');
  1336.     for i := 0 to MapOrig.Count - 1 do
  1337.       S.Add(MapOrig[i] + ',' + MapNew[i]);
  1338.     S.SaveToFile(IncludeTrailingPathDelimiter(Root) + 'obf_map.csv');
  1339.   finally
  1340.     S.Free;
  1341.   end;
  1342. end;
  1343.  
  1344. procedure ParseArgs;
  1345. var
  1346.   i: Integer;
  1347.   a, v: string;
  1348. begin
  1349.   for i := 1 to ParamCount do
  1350.   begin
  1351.     a := ParamStr(i);
  1352.     if AnsiStartsText('--root=', a) then
  1353.       GRoot := Copy(a, 8, MaxInt)
  1354.     else if AnsiSameText(a, '--inplace') then
  1355.       GInplace := True
  1356.     else if AnsiStartsText('--skip-names=', a) then
  1357.       GSkipNamesArg := Copy(a, 14, MaxInt)
  1358.     else if AnsiStartsText('--seed=', a) then
  1359.     begin
  1360.       v := Copy(a, 8, MaxInt);
  1361.       try
  1362.         GSeed := StrToInt(v);
  1363.         GSeedGiven := True;
  1364.       except
  1365.         on E: Exception do
  1366.         begin
  1367.           WriteLn('Invalid --seed value: ', v);
  1368.           Halt(1);
  1369.         end;
  1370.       end;
  1371.     end
  1372.     else if (a='-h') or (a='--help') then
  1373.     begin
  1374.       WriteLn('PasObf - simple Pascal identifier obfuscator (declaration-based)');
  1375.       WriteLn('  --root=PATH         Root folder to process (recursively)');
  1376.       WriteLn('  --inplace           Overwrite files (default: write .obf copies)');
  1377.       WriteLn('  --seed=N            PRNG seed for deterministic mapping');
  1378.       WriteLn('  --skip-names=...    Comma-separated list of names to protect');
  1379.       Halt(0);
  1380.     end;
  1381.   end;
  1382.  
  1383.   if GRoot = '' then
  1384.   begin
  1385.     WriteLn('Usage: PasObf --root=PATH [options]');
  1386.     Halt(1);
  1387.   end;
  1388. end;
  1389.  
  1390. function GeneratePolymorphicXorStr: string;
  1391. var
  1392.   funcName, keyVar, loopVar, junkVar: string;
  1393.   junkCode: string;
  1394.   sl: TStringList;
  1395. begin
  1396.   // Generate random names for the function and its variables
  1397.   funcName := 'fn_' + GenName(8);
  1398.   GXorStrFuncName := funcName; // Store globally for RewriteFile
  1399.   keyVar   := 'k_' + GenName(6);
  1400.   loopVar  := 'i_' + GenName(6);
  1401.   junkVar  := 'j_' + GenName(6);
  1402.  
  1403.   // Create some useless "junk" code to alter the function's signature
  1404.   case Random(3) of
  1405.     0: junkCode := '    ' + junkVar + ' := ' + junkVar + ' + ' + loopVar + ' and $FF;';
  1406.     1: junkCode := '    ' + junkVar + ' := (' + junkVar + ' * 3) xor ' + loopVar + ';';
  1407.     2: junkCode := '    ' + junkVar + ' := ' + junkVar + ' - ' + loopVar + ';';
  1408.   end;
  1409.  
  1410.   sl := TStringList.Create;
  1411.   try
  1412.     sl.Add('function ' + funcName + '(const s: array of Byte): string;');
  1413.     sl.Add('const');
  1414.     sl.Add('  ' + keyVar + ' = ''%%XOR_KEY%%'';');
  1415.     sl.Add('var');
  1416.     sl.Add('  ' + loopVar + ', ' + junkVar + ': Integer;');
  1417.     sl.Add('begin');
  1418.     sl.Add('  ' + junkVar + ' := 0;');
  1419.     sl.Add('  SetLength(Result, Length(s));');
  1420.     sl.Add('  if Length(' + keyVar + ') = 0 then');
  1421.     sl.Add('  begin');
  1422.     sl.Add('    for ' + loopVar + ' := 0 to High(s) do Result[' + loopVar + '+1] := Chr(s['+ loopVar +']);');
  1423.     sl.Add('    Exit;');
  1424.     sl.Add('  end;');
  1425.     sl.Add('  for ' + loopVar + ' := 0 to High(s) do');
  1426.     sl.Add('  begin');
  1427.     sl.Add('    Result[' + loopVar + '+1] := Chr(s[' + loopVar + '] xor Ord(' + keyVar + '[(' + loopVar + ' mod Length(' + keyVar + ')) + 1]));');
  1428.     sl.Add(junkCode);
  1429.     sl.Add('  end;');
  1430.     sl.Add('end;');
  1431.     Result := sl.Text;
  1432.   finally
  1433.     sl.Free;
  1434.   end;
  1435. end;
  1436.  
  1437. var
  1438.   Files: TStringList;
  1439.   MapOrig, MapNew: TStringList;
  1440.   Response: string;
  1441.   namesToSkip: TStringList;
  1442.   aName: string;
  1443.   i: Integer;
  1444.  
  1445. begin
  1446.   InitKeywords;
  1447.   ParseArgs;
  1448.   if GSeedGiven then RandSeed := GSeed else Randomize;
  1449.  
  1450.   // Generate a random XOR key for this run
  1451.   SetLength(GXorKey, 16);
  1452.   for i := 1 to Length(GXorKey) do
  1453.     GXorKey[i] := Chr(Random(94) + 33); // Printable ASCII chars
  1454.  
  1455.   // Generate the polymorphic function ONCE for this run
  1456.   GPolymorphicFuncCode := GeneratePolymorphicXorStr;
  1457.  
  1458.   if GInplace then
  1459.   begin
  1460.     WriteLn('WARNING: The --inplace flag will overwrite your files in place.');
  1461.     WriteLn('           A .bak is created then removed on success.');
  1462.     Write('Proceed? [y/N]: ');
  1463.     ReadLn(Response);
  1464.     if not (AnsiSameText(Response, 'y') or AnsiSameText(Response, 'yes')) then
  1465.       Halt(0);
  1466.   end;
  1467.  
  1468.   PublicNames  := TStrSet.Create;
  1469.   DeclaredHere := TStrSet.Create;
  1470.   SkipNames    := TStrSet.Create;
  1471.   Files := TStringList.Create;
  1472.   MapOrig := TStringList.Create;
  1473.   MapNew := TStringList.Create;
  1474.   namesToSkip := TStringList.Create;
  1475.  
  1476.   try
  1477.     // The name is now random, but we add the base name to the skip list
  1478.     // to avoid renaming any legitimate functions that happen to be called 'XorStr'.
  1479.     SkipNames.Add('XorStr');
  1480.  
  1481.     if GSkipNamesArg <> '' then
  1482.     begin
  1483.       namesToSkip.CommaText := GSkipNamesArg;
  1484.       for aName in namesToSkip do
  1485.         SkipNames.Add(aName);
  1486.     end;
  1487.  
  1488.     SplitExtPasFiles(GRoot, Files, True);
  1489.     if Files.Count = 0 then
  1490.     begin
  1491.       WriteLn('No .pas/.pp files found under ', GRoot);
  1492.       Halt(2);
  1493.     end;
  1494.  
  1495.     WriteLn('Collecting names to protect from obfuscation...');
  1496.     InitSkipList;
  1497.     CollectIdentifierLikeStrings(Files);
  1498.     CollectWrapperVarsFromGetProcAddress(Files);
  1499.     CollectTypeNames(Files);
  1500.     CollectAllPublicNames(Files);
  1501.     WriteLn('Collecting all declared identifiers...');
  1502.     CollectDeclaredHere(Files);
  1503.     WriteLn('Building obfuscation map...');
  1504.     BuildMap(Files, MapOrig, MapNew);
  1505.  
  1506.     if not GInplace then
  1507.       SaveMapCsv(GRoot, MapOrig, MapNew);
  1508.  
  1509.     RewriteAll(Files, MapOrig, MapNew, GInplace);
  1510.  
  1511.     WriteLn('Done.');
  1512.     WriteLn('String literals were encrypted with key: ', GXorKey);
  1513.     if not GInplace then
  1514.       WriteLn('Map written to ', IncludeTrailingPathDelimiter(GRoot), 'obf_map.csv');
  1515.   finally
  1516.     PublicNames.Free;
  1517.     DeclaredHere.Free;
  1518.     SkipNames.Free;
  1519.     Files.Free;
  1520.     MapOrig.Free;
  1521.     MapNew.Free;
  1522.     namesToSkip.Free;
  1523.   end;
  1524. end.
  1525.  
Advertisement
Add Comment
Please, Sign In to add comment