Page 1 of 3

Can't override canvas drawing correctly

Posted: Sun Oct 13, 2024 12:43 pm
by Fvert
I asked chatGPT how to make a random canvas fingerprint and he replied with this text. For some reason on the line context2D := CanvasPrototype.ExecuteFunction(OriginalGetContext, arguments); always returns nil, no matter what I put there (chatGPT wrote context2D := obl.ExecuteFunction(obj, arguments);). Can you tell me how to do it correctly? Thanks for your help.

Code: Select all

program ConsoleBrowser;

{$I cef.inc}

uses
//    FastMM4,
    Windows,
    System.SysUtils,System.Classes, uCEFApplicationCore, uCEFInterfaces, uCEFTypes,
    uCEFProcessMessage, uCEFConstants, uCEFv8Handler, uCEFv8Value;

{$IFDEF WIN32}
  // CEF3 needs to set the LARGEADDRESSAWARE ($20) flag which allows 32-bit processes to use up to 3GB of RAM.
  {$SetPEFlags $20}
{$ENDIF}


type
  TCanvasFingerprintV8Handler = class(TCefv8HandlerOwn)
  protected
    function Execute(const name: ustring; const obj: ICefv8Value;
      const arguments: TCefv8ValueArray; var retval: ICefv8Value; var exception: ustring): Boolean; override;
  end;


var   errmsg   : string;
      OriginalGetContext:ICefv8Value;
      CanvasPrototype:ICefv8Value;
      

procedure AddSave(SaveS,fn:AnsiString;Er:boolean);
var fs:TFileStream; crl:AnsiString;
begin
    crl:=#13#10; fs:=nil;
    try
     if not fileExists(fn) then begin
      fs:=TFileStream.Create(fn,fmCreate or fmOpenWrite);
      fs.WriteBuffer(SaveS[1], Length(SaveS));
     end else begin
      fs:=TFileStream.Create(fn,fmOpenWrite);
      if (fs.Size>100000000) and Er then fs.Size:=0;
      fs.Seek(0,soFromEnd);
      if fs.Size>0 then
        fs.WriteBuffer(crl[1],2);
      fs.WriteBuffer(SaveS[1], Length(SaveS));
     end;
    finally  freeandnil(fs)  end;
end;




function TCanvasFingerprintV8Handler.Execute(const name: ustring; const obj: ICefv8Value;
  const arguments: TCefv8ValueArray; var retval: ICefv8Value; var exception: ustring): Boolean;
var
  context2D, GetImageDataFunc, OriginalGetImageDataFunc: ICefv8Value;
  i, r, g, b, a: Integer;
  sx, sy, sw, sh, pixelCount, randomPixelIndex1, randomPixelIndex2: Integer;
  imageDataArray, pixelDataArray: ICefv8Value;

begin
  Result := False;

  // Intercept the getContext call
  if name = 'getContext' then begin
    if (Length(arguments) = 1) and (arguments[0].GetStringValue = '2d') then
    begin
      // Call the original getContext and get CanvasRenderingContext2D
      context2D := CanvasPrototype.ExecuteFunction(OriginalGetContext, arguments);
      if context2D=nil then       
       AddSave(DateToStr(Time)+' '+'nil','test4.txt',true);
      if context2D.IsObject then begin


        // Intercept getImageData on the CanvasRenderingContext2D instance
        GetImageDataFunc := context2D.GetValueByKey('getImageData');

        if GetImageDataFunc.IsFunction then
        begin
          // Save the original getImageData
          OriginalGetImageDataFunc := GetImageDataFunc;

          // Override getImageData
          context2D.SetValueByKey('getImageData', TCefv8ValueRef.NewFunction('getImageData', Self), V8_PROPERTY_ATTRIBUTE_NONE);

          // Return the modified context
          retval := context2D;
          Result := True;
        end;
      end;
    end;
  end
  // Intercept the getImageData call
  else   if name = 'getImageData' then begin
  //  AddSave(DateToStr(Date)+' '+name,'test4.txt',true);
    if Length(arguments) = 4 then
    begin
      // Getting parameters of call getImageData
      sx := arguments[0].GetIntValue;
      sy := arguments[1].GetIntValue;
      sw := arguments[2].GetIntValue;
      sh := arguments[3].GetIntValue;

      // Call the original getImageData to get the real data
      imageDataArray := obj.GetValueByKey('getImageData').ExecuteFunction(obj, arguments);

      if (imageDataArray <> nil) and imageDataArray.IsObject then begin
        // Get an array of pixels
        pixelDataArray := imageDataArray.GetValueByKey('data');

        if pixelDataArray.IsArray then
        begin
          pixelCount := pixelDataArray.GetArrayLength div 4;  // Количество пикселей (каждый пиксель — это 4 значения: RGBA)

          // Generate two random indices for pixels
          randomPixelIndex1 := Random(pixelCount) * 4;  // Multiply by 4 because each pixel consists of 4 values (R, G, B, A)
          randomPixelIndex2 := Random(pixelCount) * 4;

          // Modify the first random pixel
          pixelDataArray.SetValueByIndex(randomPixelIndex1, TCefv8ValueRef.NewInt(255));      // Changing the red channel
          pixelDataArray.SetValueByIndex(randomPixelIndex1 + 1, TCefv8ValueRef.NewInt(0));    // Changing the green channel
          pixelDataArray.SetValueByIndex(randomPixelIndex1 + 2, TCefv8ValueRef.NewInt(0));    // Changing the blue channel
          pixelDataArray.SetValueByIndex(randomPixelIndex1 + 3, TCefv8ValueRef.NewInt(255));  // Leave the alpha channel unchanged

          // Let's modify the second random pixel
          pixelDataArray.SetValueByIndex(randomPixelIndex2, TCefv8ValueRef.NewInt(0));        // Changing the red channel
          pixelDataArray.SetValueByIndex(randomPixelIndex2 + 1, TCefv8ValueRef.NewInt(255));  // Changing the green channel
          pixelDataArray.SetValueByIndex(randomPixelIndex2 + 2, TCefv8ValueRef.NewInt(0));    // Changing the blue channel
          pixelDataArray.SetValueByIndex(randomPixelIndex2 + 3, TCefv8ValueRef.NewInt(255));  // Leave the alpha channel unchanged

          // Return the modified data back
          retval := imageDataArray;
          Result := True;
        end;
      end;
    end;
  end;
end;



procedure GlobalCEFApp_OnContextCreated(const browser: ICefBrowser; const frame: ICefFrame; const context: ICefv8Context);
var
  Global, GetContextFunc, GetImageDataFunc: ICefv8Value;
  Handler: ICefv8Handler;
begin
  if GlobalCEFApp.ProcessType = ptRenderer then begin
    Global := context.GetGlobal;

    // Hijacking the getContext method on the canvas prototype
    CanvasPrototype := Global.GetValueByKey('HTMLCanvasElement').GetValueByKey('prototype');

    if (CanvasPrototype <> nil) and CanvasPrototype.IsObject then
    begin
      Handler := TCanvasFingerprintV8Handler.Create;

      // Intercept getContext
      GetContextFunc := CanvasPrototype.GetValueByKey('getContext');
      if GetContextFunc.IsFunction then begin
        // Save the original getContext method
        OriginalGetContext := GetContextFunc;
      
        // Override getContext to capture CanvasRenderingContext2D
        CanvasPrototype.SetValueByKey('getContext', TCefv8ValueRef.NewFunction('getContext', Handler), V8_PROPERTY_ATTRIBUTE_NONE);
      end;
    end; 
  end;
end;


begin
//try
  GlobalCEFApp                            := TCefApplicationCore.Create;

  GlobalCEFApp.SitePerProcess := True;
  GlobalCEFApp.WindowlessRenderingEnabled := True;
  GlobalCEFApp.EnableMediaStream          := False;
  GlobalCEFApp.EnableSpeechInput          := False;
  GlobalCEFApp.ShowMessageDlg             := False;
  GlobalCEFApp.BlinkSettings              := 'hideScrollbars';


  GlobalCEFApp.OnContextCreated:=            GlobalCEFApp_OnContextCreated;



  GlobalCEFApp.StartSubProcess;
  DestroyGlobalCEFApp;
end.




Re: Can't override canvas drawing correctly

Posted: Sun Oct 13, 2024 2:52 pm
by salvadordf
Hi,

The code you posted mixes code from several CEF4Delphi demos about JavaScript integration and it tries to mess with native prototypes. :shock:

Perhaps this answer can help you if you try to do the same with JavaScript :
https://stackoverflow.com/questions/42111341/how-can-i-override-a-native-js-function-canvass-getcontext-in-pure-javasc

If you insist doing it using CEF I would recommend learning about the JavaScript integration in CEF first instead of trying to get a quick answer with ChatGPT :
https://bitbucket.org/chromiumembedded/cef/wiki/JavaScriptIntegration.md
https://github.com/salvadordf/CEF4Delphi/blob/2b69873d737c435d8e43d56949c232debb651eed/demos/Delphi_VCL/JavaScript/JSExtension/uJSExtension.pas#L85

The demos in the "/demos/Delphi_VCL/JavaScript/" directory show how to implement what's explained in JavaScriptIntegration.md

Re: Can't override canvas drawing correctly

Posted: Sun Oct 13, 2024 7:09 pm
by Fvert
If I've got it right, it should be like this:

Code: Select all


program ConsoleBrowser;

{$I cef.inc}

uses
//    FastMM4,
    Windows,
    System.SysUtils,System.Classes, uCEFApplicationCore, uCEFInterfaces, uCEFTypes,
    uCEFProcessMessage, uCEFConstants, uCEFv8Handler, uCEFv8Value,uCEFMiscFunctions;

{$IFDEF WIN32}
  // CEF3 needs to set the LARGEADDRESSAWARE ($20) flag which allows 32-bit processes to use up to 3GB of RAM.
  {$SetPEFlags $20}
{$ENDIF}


var   errmsg   : string;
      

procedure GlobalCEFApp_OnWebKitInitialized;
var
  JSCode: string;
//  TempHandler       : ICefv8Handler;
begin

  JSCode :=
    '(function() {' +
    '    const originalToDataURL = HTMLCanvasElement.prototype.toDataURL;' +
    '    HTMLCanvasElement.prototype.toDataURL = function() {' +
    '        const context = this.getContext("2d");' +
    '        const imageData = context.getImageData(0, 0, this.width, this.height);' +
    '        for (let i = 0; i < 10; i++) {' +
    '            const x = Math.floor(Math.random() * this.width);' +
    '            const y = Math.floor(Math.random() * this.height);' +
    '            const index = (y * this.width + x) * 4;' +
    '            imageData.data[index] = Math.random() * 255;' +
    '            imageData.data[index + 1] = Math.random() * 255;' +
    '            imageData.data[index + 2] = Math.random() * 255;' +
    '            imageData.data[index + 3] = 255;' +
    '        }' +
    '        context.putImageData(imageData, 0, 0);' +
    '        return originalToDataURL.apply(this, arguments);' +
    '    };' +
    '})();';


    CefRegisterExtension('v8/test', JSCode, nil);

end;


begin
//try
//  ReportMemoryLeaksOnShutdown := True;
  GlobalCEFApp                            := TCefApplicationCore.Create;
//  GlobalCEFApp.AddCustomCommandLine('process-per-site','1');

  GlobalCEFApp.SitePerProcess := True;
  GlobalCEFApp.WindowlessRenderingEnabled := True;
  GlobalCEFApp.EnableMediaStream          := False;
  GlobalCEFApp.EnableSpeechInput          := False;
  GlobalCEFApp.ShowMessageDlg             := False;
  GlobalCEFApp.BlinkSettings              := 'hideScrollbars';

  GlobalCEFApp.OnWebKitInitialized        := GlobalCEFApp_OnWebKitInitialized;

  GlobalCEFApp.StartSubProcess;
  DestroyGlobalCEFApp;

end.

But anything more complex than the js example in JSSimpleExtension is very slow to start, and doesn't get to page load at all.

Code: Select all

  TempExtensionCode := 'var test;' +
                       'if (!test)' +
                       '  test = {};' +
                       '(function() {' +
                       '  test.myval = ' + quotedstr('My Value!') + ';' +
                       '})();';

Re: Can't override canvas drawing correctly

Posted: Wed Oct 16, 2024 11:48 am
by sodlf159
I also had a lot of trouble with Webgl, Canvas, and Audio Fingerprint, but I solved it.
I also researched for a week, but in the end I succeeded and it turned out well.

https://gist.github.com/ergcode/b9f9343e02137bb1e300753eae603834
Please refer to this for conversion.

Re: Can't override canvas drawing correctly

Posted: Wed Oct 16, 2024 2:50 pm
by Fvert
sodlf159 wrote: Wed Oct 16, 2024 11:48 am I also had a lot of trouble with Webgl, Canvas, and Audio Fingerprint, but I solved it.
I also researched for a week, but in the end I succeeded and it turned out well.

https://gist.github.com/ergcode/b9f9343e02137bb1e300753eae603834
Please refer to this for conversion.
Could you please give an example of your code, especially interested in how you embed js code, because I have anything more complicated than examples - not embedded and there is an infinite loading of the page.


For example, how can I use this technology to override onload to call alert when the page loads? This is not obvious to me.

Re: Can't override canvas drawing correctly

Posted: Thu Oct 17, 2024 12:49 am
by Fvert
sodlf159 wrote: Wed Oct 16, 2024 11:48 am ...
Have you checked with this site? https://browserleaks.com/canvas

Re: Can't override canvas drawing correctly

Posted: Thu Oct 17, 2024 4:37 am
by sodlf159
ok

Re: Can't override canvas drawing correctly

Posted: Thu Oct 17, 2024 10:56 am
by Fvert
sodlf159 wrote: Thu Oct 17, 2024 4:37 am I can tell you separately, but if the purpose is to divert website traffic, we can help each other.

Why bypass the canvas?

https://ibb.co/JypT92V
Just for self-education. I don't understand why the override doesn't work. I have long been interested in this point with canvas fingerprint as a special case. I think it interests not only me, many people have also asked on the forum.

Re: Can't override canvas drawing correctly

Posted: Thu Oct 17, 2024 11:59 am
by sodlf159
OK

Re: Can't override canvas drawing correctly

Posted: Thu Oct 17, 2024 1:10 pm
by Fvert
sodlf159 wrote: Thu Oct 17, 2024 11:59 am chat gpt

https://gist.github.com/ergcode/b9f9343e02137bb1e300753eae603834

CEF4Delphi
All you have to do is ask to convert it to this method.

Chromium1BeforeResourceLoad OR Chromium1BeforeBrowse

If it doesn't work, please tell me.

Code: Select all

procedure TJSSimpleExtensionFrm.Chromium1BeforeBrowse(Sender: TObject;
  const browser: ICefBrowser; const frame: ICefFrame;
  const request: ICefRequest; user_gesture, isRedirect: Boolean;
  out Result: Boolean);
var tmpSL:TStringList;
begin
{  if frame.IsMain then begin
    tmpSL:=TStringList.Create;
    tmpSL.LoadFromFile('webgl-detection-bypass.js');
    frame.ExecuteJavaScript(tmpSL.Text, frame.Url, 0);
    tmpSL.Free;
  end;      }
 Result := False;
end;

procedure TJSSimpleExtensionFrm.Chromium1BeforeResourceLoad(Sender: TObject;
  const browser: ICefBrowser; const frame: ICefFrame;
  const request: ICefRequest; const callback: ICefCallback;
  out Result: TCefReturnValue);
var tmpSL:TStringList;
begin
  if frame.IsMain then begin
    tmpSL:=TStringList.Create;
    tmpSL.LoadFromFile('webgl-detection-bypass.js');
    frame.ExecuteJavaScript(tmpSL.Text, frame.Url, 0);
    tmpSL.Free;
  end;
  Result := RV_CONTINUE;
end;
In the script I changed only the beginning and end () => { to (function() { and } to })(); I don't understand what is wrong.

Maybe there should be some additional browser settings?