Search Engines

Contents

    Introduction

    In Firefox, search engines have been widely abused in the past. The curent complexity comes from hijacking-protection. (source)

    What does NOT work anymore

    So, how does it works?

    Decoding/Encoding search.json.mozlz4

    search.json.mozlz4 has a magic number header ("mozLz40\0") followed by the content itself, encoded using LZ4 algorithm. That's why, in order to decompress the file, the magic number needs to be skiped, then the remaining bytes can be extracted using LZ4 algorithm.

    This python program does the job well (source)

    #!/usr/bin/env python
    from sys import stdin, stdout, argv, stderr
    import os
    try:
        import lz4.block as lz4
    except ImportError:
        import lz4
    
    stdin = os.fdopen(stdin.fileno(), 'rb')
    stdout = os.fdopen(stdout.fileno(), 'wb')
    
    if argv[1:] == ['-c']:
        stdout.write(b'mozLz40\0' + lz4.compress(stdin.read()))
    elif argv[1:] == ['-d']:
        assert stdin.read(8) == b'mozLz40\0'
        stdout.write(lz4.decompress(stdin.read()))
    else:
        stderr.write('Usage: %s -c|-d < infile > outfile\n' % argv[0])
        stderr.write('Compress or decompress Mozilla-flavor LZ4 files.\n\n')
        stderr.write('Examples:\n')
        stderr.write('\t%s -d < infile.json.mozlz4 > outfile.json\n' % argv[0])
        stderr.write('\t%s -c < infile.json > outfile.json.mozlz4\n' % argv[0])
        exit(1)
    

    Requires

    Reverse engineering of search.json.mozlz4 Json schema

    Hash computation

    Hashes are used to ensure that the file has been updated by Firefox itself. So computing hashes has been designed to be difficult, in order to prevent malicious program from updating silently search.json.mozlz4 file.

    Source code

    Using jsconsole

    Using Bash commands

    # getVerificationHash("Qwant")
    name=Qwant
    
    # OS.Path.basename(OS.Constants.Path.profileDir) → profile dependent, of course
    profileDir="fbhzwefm.default"
    
    # Services.appinfo.name
    appName="Firefox"
    
    # Copy/Pasted disclaimer from https://hg.mozilla.org/mozilla-central/file/tip/toolkit/components/search/SearchEngine.jsm#l228
    disclaimer="By modifying this file, I agree that I am doing so only within $appName itself, using official, user-driven search engine selection processes, and in a way which does not circumvent user consent. I acknowledge that any attempt to change this file from outside of $appName is a malicious act, and will be responded to accordingly."
    
    salt=$profileDir$name$(echo $disclaimer | sed "s/\$appName/$appName/g")
    
    printf "$salt" | openssl sha256 -binary | base64
    

    Appendix

    Here is getVerificationHash function from SearchEngine.jsm.

    function getVerificationHash(name) {
      let disclaimer =
        "By modifying this file, I agree that I am doing so " +
        "only within $appName itself, using official, user-driven search " +
        "engine selection processes, and in a way which does not circumvent " +
        "user consent. I acknowledge that any attempt to change this file " +
        "from outside of $appName is a malicious act, and will be responded " +
        "to accordingly.";
    
      let salt =
        OS.Path.basename(OS.Constants.Path.profileDir) +
        name +
        disclaimer.replace(/\$appName/g, Services.appinfo.name);
    
      let converter = Cc[
        "@mozilla.org/intl/scriptableunicodeconverter"
      ].createInstance(Ci.nsIScriptableUnicodeConverter);
      converter.charset = "UTF-8";
    
      // Data is an array of bytes.
      let data = converter.convertToByteArray(salt, {});
      let hasher = Cc["@mozilla.org/security/hash;1"].createInstance(
        Ci.nsICryptoHash
      );
      hasher.init(hasher.SHA256);
      hasher.update(data, data.length);
    
      return hasher.finish(true);
    }
    

    Proudly Powered by Zim 0.75.2.

    Template by Etienne Gandrille, based on ZeroFiveEight and using JQuery Toc Plugin.