maxisme / crypter

This extension locally encrypts and decrypts your Facebook messages using AES encryption along with a preset password.
https://crypter.co.uk
84 stars 18 forks source link

How did Facebook block crypter? #4

Open burtonator opened 8 years ago

burtonator commented 8 years ago

How did the block technically work?

Did they block ALL encrypted messages or did they somehow just block the extension? How are they blocking the extension when it isn't included on their site?

maxisme commented 8 years ago

A change in the DOM.

The input where one writes their message used to be a <textarea> and now it is an editable <div>. Before I was able to take the data out of the <textarea> and replace it with the encrypted version. Now doing that does not work. And I am having to automate the keypress of an editable <div>.

Auto-Encrypt Before I was able to manipulate(encrypt) the message before it was sent to Facebook on the Enter keypress. Now I have to prevent the code being submitted and then encrypt it. But I still need to be able to submit the message automatically after encrypting it by simulating the enter press.

burtonator commented 8 years ago

Is it possible to bring up a local textarea or use the extension to generate a text area, then edit it there, then write it to he div encrypted?

Interesting attempt at a block. Seems eventually possible to get past.

maxisme commented 8 years ago

Yes, it is! Someone mentioned that using an <iframe> to my server which has a <textarea> might even be a good idea! But I can't do any of that until I am able to automate an Enter keydown! :-1:

Yes I think it will be but I need help!

sadreck commented 8 years ago

Hi Max,

I think you might be over-complicating this a bit. I'm the author of a similar tool (exactly for facebook) and it has been working for the past 2 years (the main difference between our implementations is that my doesn't use a preset password).

I assume you made it into a Chrome extension because you couldn't add an event listener to the ENTER key before the event listeners that Facebook adds itself. Having said this, I don't see the reason why you are using this to prevent the default event bubbling. Why not just replace the inner plain text with the encrypted one like this and then let Facebook handle everything else?

burtonator commented 8 years ago

@maxisme

Ah.. ok. Well I have a work around / potential solution for you. What you can do is generate a raw event and fire it which generates the keyboard event. I have some that does this for phantomjs and it works for DOM across the board. I can try to pull it out of mothballs if you like but it might take me a while.

Long story short, you re-create the actual DOM event and make a synthetic one and send it.. works fine.

maxisme commented 8 years ago

Hello Pavel,

Thank you for getting in touch!

Yes I think I probably am... Haha. Oh awesome. Have you tested it in the last couple of days? From what I can see that seems to be the method I used to have before facebook changed their DOM.

I have tried your method by replacing this with $(node).val(encrypted); (what I was doing before facebook changed everything) and removing the "preventing default" stuff. But no luck :-1:

sadreck commented 8 years ago

Hi Max,

That's kinda weird. I haven't tested the last couple of days (I'm on holiday - yay!) but will do this weekend and let you know.

maxisme commented 8 years ago

@burtonator Sounds brilliant. That would be so useful, if you don't mind! I am having a problem finding the actual DOM event though!?

maxisme commented 8 years ago

@sadreck Oh nice! Hope you are having nice time. Yes please let me know. Hopefully will have a fix by then and I will let you know!!

maxisme commented 8 years ago

@sadreck we both live in Brighton... Weird.

sadreck commented 8 years ago

@maxisme it's where all great ideas are born! haha

burtonator commented 8 years ago

@maxisme What you should do is to set a breakpoint in your code and then look at the event that was created. Then you just duplicate it.

Here's something similar. I think I'm doing this for mouse events but this should be a good starting point for you:


    var event = document.createEvent('MouseEvent');

    // NOTE: it turns out that we don't need to use bounding rect because we're
    // calling element.dispatchEvent which places the event directly on the
    // element we're interested in regardless of the position of the mouse
    // pointer.  I want this disabled because there are some race conditions
    // and setting this to left=0, top=0 means that I don't need to worry about
    // issues like the page elements being redrawn and shifted.
    var useBoundingRect = false;

    var rect = { left: 0, top: 0 };

    if ( useBoundingRect ) {
        rect = element.getBoundingClientRect();
    }

    // this method has a large number of parameters but for our usage
    // its pretty simple.  We only really care about the last param,
    // the element
    event.initMouseEvent(
        eventName, //event type : click, mousedown, mouseup, mouseover, mousemove, mouseout.
        true, //canBubble
        false, //cancelable
        window, //event's AbstractView : should be window
        1, // detail : Event's mouse click count
        rect.left, // screenX
        rect.top,  // screenY
        rect.left, // clientX
        rect.top,  // clientY
        false, // ctrlKey
        false, // altKey
        false, // shiftKey
        false, // metaKey
        0, // button : 0 = click, 1 = middle button, 2 = right button
        element // relatedTarget : Only used with some event types (e.g. mouseover and mouseout). In other cases, pass null.
    );

    element.dispatchEvent( event );
maxisme commented 8 years ago

@burtonator Thank you! Is setting a breakpoint in the code supposed to help me find out the function that facebook use?

maxisme commented 8 years ago

@burtonator I have tried the equivalent to this with no luck!

burtonator commented 8 years ago

@maxisme Sorry. I meant to say you can add an event listener for [enter] then set a break point, look at the event that is sent. Then you can create a synthetic one and use it with dispatchEvent.

burtonator commented 8 years ago

@maxisme That should definitely work.. wonder if there is some issue with bubbling or something along those lines. My strength is Java backend and while I do know enough to get bloody in the Javascript/browser world it's not my forte... but I think this would be the right path to go down.

maxisme commented 8 years ago

@burtonator When monitoring evt.keyCode in my document.addEventListener('keydown', function(evt){ it returns 0 not 13 when running the code:

var keyboardEvent = document.createEvent("KeyboardEvent");
var initMethod = typeof keyboardEvent.initKeyboardEvent !== 'undefined' ? "initKeyboardEvent" : "initKeyEvent";

keyboardEvent[initMethod](
                   "keydown", // event type : keydown, keyup, keypress
                    true, // bubbles
                    false, // cancelable
                    window, // viewArg: should be window
                    false, // ctrlKeyArg
                    false, // altKeyArg
                    false, // shiftKeyArg
                    false, // metaKeyArg
                    13, // keyCodeArg : unsigned long the virtual key code, else 0
                    0 // charCodeArgs : unsigned long the Unicode character associated with the depressed key, else 0
);
document.dispatchEvent(keyboardEvent);

So obviously facebook are blocking the keycode 13... I did find:

__d('SyntheticKeyboardEvent', ['SyntheticUIEvent', 'getEventCharCode', 'getEventKey', 'getEventModifierState'], function a(b, c, d, e, f, g, h, i, j, k) {
    'use strict';
    if (c.__markCompiled)
        c.__markCompiled();
    var l = {
        key: j,
        location: null ,
        ctrlKey: null ,
        shiftKey: null ,
        altKey: null ,
        metaKey: null ,
        repeat: null ,
        locale: null ,
        getModifierState: k,
        charCode: function(event) {
            if (event.type === 'keypress')
                return i(event);
            return 0;
        },
        keyCode: function(event) {
            if (event.type === 'keydown' || event.type === 'keyup')
                return event.keyCode;
            return 0;
        },
        which: function(event) {
            if (event.type === 'keypress')
                return i(event);
            if (event.type === 'keydown' || event.type === 'keyup')
                return event.keyCode;
            return 0;
        }
    };
    function m(n, o, p, q) {
        h.call(this, n, o, p, q);
    }
    h.augmentClass(m, l);
    f.exports = m;
}, null );

here. But I am not sure whether it is at all linked...

Also the sendkeys function is not picked up by the keydown which is interesting...

I hate Facebook scripts...


I have also tried:

                var $textBox = $(node);
        var keycode = 13;
        var press = jQuery.Event("keydown");
        press.altGraphKey = false;
        press.altKey = false;
        press.bubbles = true;
        press.cancelBubble = false;
        press.cancelable = false;
        press.charCode = keycode;
        press.clipboardData = undefined;
        press.ctrlKey = false;
        press.currentTarget = $textBox[0];
        press.defaultPrevented = true;
        press.detail = 0;
        press.eventPhase = 2;
        press.keyCode = keycode;
        press.keyIdentifier = "";
        press.keyLocation = 0;
        press.layerX = 0;
        press.layerY = 0;
        press.metaKey = false;
        press.pageX = 0;
        press.pageY = 0;
        press.returnValue = true;
        press.shiftKey = false;
        press.srcElement = $textBox[0];
        press.target = $textBox[0];
        press.type = "keydown";
        press.view = Window;
        press.which = keycode;
        $textBox.trigger(press);
maxisme commented 8 years ago

I am starting to think that automating the enter key is not the way I should be going. Instead I should be trying to replace the inputted text before it is sent.

maxisme commented 8 years ago

The issue is that the text used to submit to the chat on the keypress is not actually from here it is stored somewhere! (dodgy I know.)

Angus-McLean commented 8 years ago

Hey, I've been messing around with the changes Facebook made aswell, without any luck.. It looks like they're using their own kind of Event Subscriber and calling ".AddListener" which is a function they created. They then generate their own custom events "BaseEventEmitter". Facebook uses .keyCode and .which which are both read only properties of events, doesn't look like they do any checks to see if its an actual event to so I'm trying to just create an object with all the same properties and functions and use that. I tried renaming the addEventListener function on all types of elements hoping I could intercept the event on all listeners inside the page and maybe pass a custom Object through.

Run the following in the context of the page before any of the Facebook scripts begin executing. Disclaimer : I still haven't managed to fix this but maybe this will help

var _interfaces = [ HTMLDivElement, HTMLImageElement, HTMLUListElement, HTMLElement, HTMLDocument ];
for (var i = 0; i < _interfaces.length; i++) {
    // pass in original addEventListener
    (function(original) {
        // rename EventListener
        _interfaces[i].prototype.addEventListener = function(a,b,c){
            // if its a key event change its values
            if(a == 'keydown' || a == 'keypress' || a == 'keyup' || a == 'input'){
                console.log(arguments);
                var listenerElem = this;
                var intercept = (function () {

                    var simEv = {};
                    for(var i in arguments[0]){
                        simEv[i] = arguments[0][i];
                    }
                    console.log('intercepted', arguments, simEv);
                    arguments[0].preventDefault();
                    arguments[0].stopPropagation();
                    b.apply(listenerElem, [simEv]);
                });
                original.apply(this, [a, intercept, c])
            } else {
                // if not key event pass normal arguments
                original.apply(this, arguments)
            }
        }
    })(_interfaces[i].prototype.addEventListener);
}
maxisme commented 8 years ago

@Angus-McLean Awesome!! That looks sweet! Thank you so much. I will have a go too and will let you know if I find anything! I have been trying for so long though haa. Your method seems like the write direction for sure! I have been using chrome to stop before submitting the message and go through all the (illegible) events!

burtonator commented 8 years ago

The only problem with this is that if they change it again... then you have to reverse engineer it again. So what is really needed is a generic event capture system that will work no matter what they do as the system evolves.

Not trying to be too pessimistic.. I realize that this was a lot of hard work.

TobyColeman commented 8 years ago

I think what @maxisme is suggesting could be on the right lines. I overrode Facebook's event managing system (Arbiter) and it seemed to work quite well. You could also just monkey patch the XMLHttpRequest prototype maybe? https://github.com/TobyColeman/Whisper/tree/master/src/js/app/injected (shameless plug)

maxisme commented 8 years ago

@TobyColeman @Angus-McLean @sadreck @burtonator This is driving me crazy. I am happy to put a £50 (student price) bounty on this haha!

Tasemu commented 8 years ago

Any news on this? I found the project today and am totally gutted that its down.

Tasemu commented 8 years ago

Not sure how much this helps, but the MessengerComposer react component used to send facebook chat messages calls a function named _handleMessageSend on its component instance, which then crafts a message and calls the _sendMessage function also on its own instance to send the message. Would it be possible to replace the message in this component before 'n' is constructed? Assuming the parameter k is the message.

_handleMessageSend: function(k, l) {
            if (!this.props.canReply)
                return false;
            return this._sendMessage(function(m) {
                var n = this._messageBuilder.constructUserGeneratedMessageObject(k,          c('MercurySourceType').MESSENGER_WEB, m);
                if (l)
                    if (l.shareable_attachment)
                        n.shareable_attachment = l.shareable_attachment;
                return n;
            }
            .bind(this));
 }
Tasemu commented 8 years ago

I was able to set the _handleMessageSent function to a global variable through chrome dev tools and call it with a string as the only argument. The result was the string successfully sent through the chat. If we can get the instance of this function for a chatbox, we could pass our encrypted text to it and send the message I believe.

maxisme commented 8 years ago

Hello @Tasemu. Thank you for your interest! That looks perfect... Unfortunately Crypter is for facebook.com rather than messenger.com :-1: but I believe that there is something similar to what you are attempting that @Angus-McLean mentioned above! Let me know if you have any more ideas! And thanks again.

maxisme commented 8 years ago

I found the facebook function where they send the message. It is on line 887 (formatted) in https://static.xx.fbcdn.net/rsrc.php/v2iDeF3/yr/l/en_US/9dfBYy78YgQ.js (pastebin) . There is a variable n which holds the content of the message body:"test" where test is the message. image

I have tried changing the body to hello and then resuming the script but facebook still sends the first message test I don't know whether that is because you can't do that with chrome. Or whether the message is assigned somewhere else? But I think we are getting very close. This is all thanksed to you @Tasemu I started searching facebooks code for _sendMessage thank you!

burtonator commented 8 years ago

Is it possible to just alter the REST API call directly? I am wondering if you can monkey patch the XHR layer by copying the current functon to a new variable, then providing your own implementation.

This way when they call XHR you can see the data, and the API endpoint, and rewrite it.

maxisme commented 8 years ago

I am sorry @burtonator but I do not understand a word of that! Haha. Pardon my ignorance… I will get researching.

Thank you!

Maximilian Mitchell maxis.me (http://maxis.me)

On Tuesday, 15 March 2016 at 19:20, Kevin Burton wrote:

Is it possible to just alter the REST API call directly? I am wondering if you can monkey patch the XHR layer by copying the current functon to a new variable, then providing your own implementation. This way when they call XHR you can see the data, and the API endpoint, and rewrite it.

— You are receiving this because you were mentioned. Reply to this email directly or view it on GitHub (https://github.com/maxisme/crypter/issues/4#issuecomment-196982855)

maxisme commented 8 years ago

Okay so I have found the function where if I manipulate the variable like above it will modify the message sent!! It is on line 8936 of https://static.xx.fbcdn.net/rsrc.php/v2i_Sq3/yp/l/en_US/qHGRnnkBbvr.js (http://pastebin.com/Z20ef3Nj)

maxisme commented 8 years ago

Here is the latest version of Crypter for Chrome using iframes if you are interested. The only major thing left to do is regarding this topic. Currently with this extension you have to press enter twice; first time it creates a lock, second time to send the lock.

Tasemu commented 8 years ago

Oh wicked! i'm a little confused though, does this version work? Or is facebook still blocking it

Tasemu commented 8 years ago

Just gave it a shot then, seems to be working! oh man you legend!

maxisme commented 8 years ago

Hey guys I have finally finished a new version of Crypter for Chrome but I believe it is quite buggy and would really appreciate you testing it if you have a second. Thanks again for all the help. Oh and the code is on the Crypter 2.0 branch.