big squashed commit, finalize gshellautomator

master
EmaMaker 2021-11-05 18:23:25 +01:00
parent ea192e2d12
commit b83e8449e3
39 changed files with 630 additions and 329 deletions

View File

@ -1,10 +1,32 @@
# <b> Google Colab Automator, without Selenium! </b>
# <b> Google Cloud Shell Automator! </b>
## <b> What is this </b>
This program creates a botnet to execute arbitrary code using multiple Google Colab VMs.<br>
# <b> What is this </b>
This program creates a botnet to execute arbitrary code using multiple Google Shell Docker containers.<br>
<br>
# <b> Dependendencies </b>
This program needs some dependencies to work. All of python dependencies are already installed in the venv folder. Note that for now this makes use of xephyr to create virtual desktops and for now this only works on Linux with X11 as a display manager. The packages' names will vary from distro to distro, here are the most important ones which you will need to take care by yourself
* Xephyr (xorg-xephyr)
* TKinter (tk)
<br>
# <b> Execution </b>
From the root rolder of the project, execute
src/colab_automator.sh
it will take care of starting all the needed services in the correct
order
<br>
# <b> Explanation of services and exploit </b>
### <b> Google Colab and Qwiklabs </b>
Google Colab is a Google service which gives free, temporary, use of virtual machines with powerful NVIDIA Tesla GPUs. Normally this is used to train ML and other AI models, but nothing impedes the use of the service for other purposes, such has password cracking with hashcat or crypto mining (at least in the free version - in Google Colab Pro crypto mining is forbidden).
Google
The downside is that the account gets temporary blocked when a too andlong intense use is detected.
Qwiklabs (qwiklabs.com) is a third-party service which gives temporary Google Accounts for training in Google Cloud Shell use. Their first course (https://www.qwiklabs.com/focuses/2794?parent=catalog) is completely free, others require some in-site credits to be purchased.
Combining the two services, infinite temporary and disposable Google Accounts can be obtained from qwiklabs and used for mining on colab, until the qwiklabs session expires or the miner gets blocked by Colab. At such point, a new session can be started. That's basically free money. Obviously this can also be used to automated non-malicious tasks, as long as the Colab notebook is publicly available (e.g. as a github gist). After a certain time number of times repeating the course, the qwiklabs account might reach it's <i>quota</i> for that course and a new one needs to be created. I'll will look into a way of automating account creation in the future. For now, use temp mails from temp-mail.org when creating accounts
@ -40,11 +62,8 @@ For simpicity, and to avoid messing up the OS by clicking on the wrong stuff, bo
<br>
<br>
# <b> Failure </b>
This project apparently badly failed, since google accounts obtained through qwiklabs.com labs appear to never be able to obtain a backend on Google Colab<br>
## Possible workaround.
Go back to cpu-only and use Google Cloud Shell (which is actually what qwiklabs is meant to be used with) and create some king of botnet/userbot/viewbot for youtube, altervista or other websites. The machine will always appear to be a new one, because it actually is
# Qwiklabs monthly subscription
There is a workaround to obtain a qwiklabs monthly subscription that actually works: https://www.youtube.com/watch?v=gF6agG9kyBs (actually works).<br>
Tested on account i3z8qtab@xojxe.com, now it has monthly sub
Tested on account i3z8qtab@xojxe.com, now it has monthly sub
This is too convoluted to automated and has no use for now

Binary file not shown.

View File

@ -5,4 +5,5 @@ selenium==3.141.0
urllib3==1.26.5
SpeechRecognition==3.8.1
pydub==0.25.1
bs4
bs4
random_user_agent

View File

@ -1,152 +0,0 @@
from utils import browser_manager
from selenium import webdriver
from selenium.webdriver.common.by import By
import time
from threading import Thread, Event
from utils.global_vars import PROXY
class ColabGist(Thread):
'''
Account is a tuple of (user, pass)
Min is for how long the gist needs to be executed
Backend is the backend needed for this gist ('N': None, 'T': TPU, 'G': GPU)
For future-proofness each Gist is started in its own thread, this makes possible to run multiple gists in different threads at the same time
'''
def __init__(self, url, account, minutes=5, backend='N'):
self.url = url
self.account = account
self.minutes = minutes
self.backend = backend
self.refresh_event = Event()
self.stop_event = Event()
print("[ColabGist {}] New Colab Gist Thread created! Using account {} ".format(self.url, self.account))
def run(self):
self.run_colab()
def run_colab(self):
print("[ColabGist {}] Starting the gist".format(self.url))
self.start_browser()
self.start_session()
def sign_in(self):
print("[ColabGist {}] Signing in...".format(self.url))
try:
self.driver.get("https://accounts.google.com/o/oauth2/v2/auth/oauthchooseaccount?redirect_uri=https%3A%2F%2Fdevelopers.google.com%2Foauthplayground&prompt=consent&response_type=code&client_id=407408718192.apps.googleusercontent.com&scope=email&access_type=offline&flowName=GeneralOAuthFlow") #need a fallback for when
browser_manager.inputText(self.driver, By.CSS_SELECTOR, "#identifierId", self.account[0] + "\n")
time.sleep(1.5)
browser_manager.inputText(self.driver, By.XPATH, '//*[@id="password"]/div[1]/div/div[1]/input', self.account[1] + "\n")
time.sleep(1.5)
try:
browser_manager.clickButton(self.driver, By.XPATH, '//*[@id="accept"]')
time.sleep(1.5)
browser_manager.clickButton(self.driver, By.XPATH, '//*[@id="yDmH0d"]/c-wiz[2]/c-wiz/div/div[1]/div/div/div/div[2]/div[3]/div/div[2]/div')
time.sleep(1.5)
except:
pass
return True
except:
return False
# Run the desired Colab notebook. Logging into Google account by profile is mandatory
def start_session(self):
if not self.sign_in():
print("[ColabGist {}] Got the old login screen, trying again".format(self.url))
self.driver.quit()
self.run_colab()
return
self.driver.get(self.url)
self.start_gist()
# Leave the colab be for the specified time
starting = time.time()
print("[ColabGist {}] Executing the gist for the next {} minutes. Using Backend {}".format(self.url, self.minutes, self.backend) )
while (int(time.time() - starting))/60 < self.minutes:
if self.stop_event.is_set():
self.actual_quit()
if self.refresh_event.is_set():
self.start_gist()
if(int(time.time() - starting > 15)):
try:
if self.driver.find_elements(By.XPATH, "//*[contains(text(),'No backend')]"):
print("[ColabGist {}] This account {} has been blocked :/ try again with another one".format(self.url, self.account))
self.driver.quit()
elif self.driver.find_elements(By.XPATH, "//*[contains(text(),'Cannot connect')]"):
print("[ColabGist {}] No backend was available, maybe the service is overflooded or this account/computer is about to be blocked :(".format(self.url))
self.driver.quit()
return
except Exception as e:
print(e)
'''char = input()
if char == "t":
break
elif char == "g":
elapsed = time()-starting
elapsed_min = int(elapsed / 60)
elapsed_sec = int(elapsed % 60)
print("Elapsed time: {}m{}s".format(elapsed_min, elapsed_sec))
else:
print("Unrecognized option")'''
print("[ColabGist {}] Time's up! Closing the browser".format(self.url))
self.actual_quit()
def start_gist(self):
browser_manager.clickButton(self.driver, By.CSS_SELECTOR, "#runtime-menu-button")
browser_manager.clickButton(self.driver, By.XPATH, '//*[@id=":24"]')
time.sleep(1)
browser_manager.clickButton(self.driver, By.CSS_SELECTOR, "#accelerator")
time.sleep(1.5)
acc = self.driver.find_element_by_css_selector("#accelerator")
time.sleep(1.5)
acc.send_keys(self.backend)
acc.send_keys("\n")
time.sleep(1.5)
browser_manager.clickButton(self.driver, By.CSS_SELECTOR, "#ok")
time.sleep(1.5)
browser_manager.clickButton(self.driver, By.CSS_SELECTOR, "#runtime-menu-button")
time.sleep(1.5)
browser_manager.clickButton(self.driver, By.XPATH, '//*[@id=":22"]')
time.sleep(1.5)
browser_manager.clickButton(self.driver, By.CSS_SELECTOR, "#runtime-menu-button")
time.sleep(1.5)
browser_manager.clickButton(self.driver, By.XPATH, '//*[@id=":1t"]')
time.sleep(1.5)
browser_manager.clickButton(self.driver, By.CSS_SELECTOR, "#ok")
time.sleep(1.5)
def terminate_session(self):
# Terminate the session
browser_manager.clickButton(self.driver, By.CSS_SELECTOR, "#runtime-menu-button")
#factory reset to close session
browser_manager.clickButton(self.driver, By.XPATH, '//*[@id=":22"]')
browser_manager.clickButton(self.driver, By.CSS_SELECTOR, "#ok")
# Wait for the session to close
time.sleep(60)
def start_browser(self):
self.driver=browser_manager.start_browser()
def stop_browser(self):
# Close browser
self.driver.quit()
def actual_quit(self):
self.terminate_session()
self.stop_browser()
def quit(self):
self.stop_event.set()

View File

@ -15,8 +15,13 @@ echo "[*] Starting Xephyr on :2, contained inside :1"
DISPLAY=:1 Xephyr -br -ac -noreset -screen 800x600 :2 > /dev/null 2> /dev/null&
sleep 5
# Start a copyq server, that will be used to copy the email and password of the obtained account from the clipboard of Xephyr
echo "[*] Starting copyq server on :2"
DISPLAY=:2 copyq > /dev/null 2> /dev/null&
#echo "[*] Starting copyq server on :2"
#DISPLAY=:2 copyq > /dev/null 2> /dev/null&
#set the kbd layout to the same of :0, otherwise sometimes we get mismatched letters
#setxkbmap -display :0 -print | xkbcomp - :1 > /dev/null 2> /dev/null&
# xkbcomp :0 :1
# xkbcomp :0 :2
sleep 2
echo "[*] Now starting the actual script"

View File

@ -1,12 +1,11 @@
import subprocess
from utils import global_vars, proxy
from utils import global_vars
import time
from qwiklabs import get_account
from shell import shell
from shell.gists.gists import UserBot
print("[MAIN] Entering main script!")
p = proxy.Proxy()
while True:
p.request_new_proxy()
print(global_vars.PROXY)
time.sleep(30)
s = shell.Shell()
s.start()
s.execute_python_custom_script(UserBot())

View File

@ -1,39 +0,0 @@
# access ngrok with selenium, get the first ip/port combination
# https://dashboard.ngrok.com/endpoints/status
# automatically accepts key and inputs password
# sshpass -p hellogoodbye ssh -D 1337 -q -C -N root@ip -p port -o "StrictHostKeyChecking no"
from utils import browser_manager
from selenium.webdriver.common.by import By
import time
from colab import colab
class Ngrok:
def __init__(self, account):
self.account = account
def start_browser(self):
self.driver = browser_manager.start_browser(headless=True)
def close_browser(self):
browser_manager.quit_browser(self.driver)
def access_ngrok(self):
self.driver.get('https://dashboard.ngrok.com/endpoints/status')
time.sleep(5)
browser_manager.inputText(self.driver, By.CSS_SELECTOR, '#email', self.account[0])
time.sleep(1)
browser_manager.inputText(self.driver, By.CSS_SELECTOR, '#password', self.account[1])
time.sleep(1)
browser_manager.clickButton(self.driver, By.XPATH, '/html/body/div[2]/div/section/main/div/div/div[2]/div[1]/div/form/div[3]/div/div/div/button')
time.sleep(5)
# Assumes access_ngrok has already been called
def get_proxy_ip(self):
self.driver.get('https://dashboard.ngrok.com/endpoints/status')
time.sleep(10) #takes a bit to load, just in case
proxy_line = self.driver.find_element_by_xpath('/html/body/div[2]/div/section/section/main/div/div/div/main/div[2]/div/div/div/div/div/table/tbody/tr[2]/td[3]').text
# strip down the tcp:// part at the start, it's not needed
proxy = proxy_line[6:]
return proxy

BIN
src/qwiklabs/__init__.pyc Normal file

Binary file not shown.

Binary file not shown.

View File

@ -132,69 +132,119 @@ if __name__ == "__main__":
pyautogui.click()
bashCommand = "copyq clipboard"
# Copy username to clipboard
time.sleep(20)
# print("Copying email")
pyautogui.moveTo(ui.qwiklabs_gui.EMAIL_COPY_BTN[0], ui.qwiklabs_gui.EMAIL_COPY_BTN[1], 3, pyautogui.easeOutQuad)
pyautogui.click()
g_email = subprocess.check_output(['bash','-c', bashCommand]).decode('utf-8')
# Now the lab should be started, we can quit. Creds will be obtained by selenium
# Copy password to clipboard
time.sleep(5)
# print("Copying password")
pyautogui.moveTo(ui.qwiklabs_gui.PASSWORD_COPY_BTN[0], ui.qwiklabs_gui.PASSWORD_COPY_BTN[1], 3, pyautogui.easeOutQuad)
pyautogui.click()
g_password = subprocess.check_output(['bash','-c', bashCommand]).decode('utf-8')
time.sleep(5)
# a possible way of finding out we haven't got the password, therefore the account was probably blocked or there was a connection issue
if '@' in g_email:
print(g_email, g_password)
else:
# account_list.mark_account_for_deletition(account_email)
print(False)
# bashCommand = "copyq clipboard"
# # Copy username to clipboard
# time.sleep(20)
# # print("Copying email")
# pyautogui.moveTo(ui.qwiklabs_gui.EMAIL_COPY_BTN[0], ui.qwiklabs_gui.EMAIL_COPY_BTN[1], 3, pyautogui.easeOutQuad)
# pyautogui.click()
# g_email = subprocess.check_output(['bash','-c', bashCommand]).decode('utf-8')
# # Copy password to clipboard
# time.sleep(5)
# # print("Copying password")
# pyautogui.moveTo(ui.qwiklabs_gui.PASSWORD_COPY_BTN[0], ui.qwiklabs_gui.PASSWORD_COPY_BTN[1], 3, pyautogui.easeOutQuad)
# pyautogui.click()
# g_password = subprocess.check_output(['bash','-c', bashCommand]).decode('utf-8')
# time.sleep(5)
# # a possible way of finding out we haven't got the password, therefore the account was probably blocked or there was a connection issue
# if '@' in g_email:
# print(g_email, g_password)
# else:
# # account_list.mark_account_for_deletition(account_email)
# print(False)
def get_google_account():
from utils import global_vars
from selenium.webdriver.common.by import By
from utils.browser_manager import clickButton, inputText, expand_shadow_element, start_browser
import subprocess
import json
import time
if not global_vars.TEST_ACCOUNT:
print("[QL_GetAccount] Getting new google account..." )
account = global_vars.ql_list.request_new_account()
res = subprocess.check_output(['bash','-c', "src/qwiklabs/get_account.sh {} {} ".format(str(global_vars.PROXY), account)]).decode('utf-8')
if res == 'False' or 'False' in res:
res = bool(res)
# Maybe if we failed there was a connection issue, so ping qwiklabs.com to check
# If ping is successful the account is out of quota
if global_vars.PROXY is False:
r = requests.get("https://qwiklabs.com")
else :
proxyDict = {
"socksVersion" : 5,
"socks" : global_vars.PROXY
}
r = requests.get("https://qwiklabs.com", proxies=proxyDict)
# ping was unsuccessful, delete the account
if r.stats_code == 200:
print("[QL_GetAccount] Something went wrong, trying again")
return get_google_account()
else:
global_vars.account_list.mark_account_for_deletition(account)
else:
res = tuple(res.rsplit())
print("[QL_GetGAccount] {}".format(res))
return res
else:
if global_vars.TEST_ACCOUNT:
print("[QL_GetAccount] Using Test Account {}".format(global_vars.TEST_ACCOUNT) )
return global_vars.TEST_ACCOUNT
driver = start_browser()
driver.get(global_vars.TEST_ACCOUNT[2])
return global_vars.TEST_ACCOUNT[0], global_vars.TEST_ACCOUNT[1], global_vars.TEST_ACCOUNT[2], driver
else:
print("[QL_GetAccount] Getting new google account..." )
#TODO: Handle failure in getting the account
if global_vars.TEST_QWIKLABS_ACCOUNT is False:
print("[QL_GetAccount] Using TEST Qwiklabs account to get it")
account = global_vars.ql_list.request_new_account()
subprocess.call(['bash','-c', "src/qwiklabs/get_account.sh {} {} ".format(str(global_vars.PROXY), account)])
else:
print("[QL_GetAccount] Using Qwiklabs test account")
account = global_vars.TEST_QWIKLABS_ACCOUNT
driver = start_browser()
driver.get("https://www.qwiklabs.com/focuses/2794?catalog_rank=%7B%22rank%22%3A2%2C%22num_filters%22%3A0%2C%22has_search%22%3Atrue%7D&parent=catalog&search_id=12598152")
time.sleep(5)
clickButton(driver, By.XPATH, '/html/body/div[1]/div[1]/ql-toolbar/div[2]/a[2]')
time.sleep(5)
inputText(driver, By.CSS_SELECTOR, "#user_email", account)
inputText(driver, By.CSS_SELECTOR, "#user_password", "hellogoodbye" + '\n')
time.sleep(5)
panel = driver.find_element_by_xpath('/html/body/main/div/ql-drawer-container/ql-drawer-content/ql-drawer-container/ql-drawer[1]/ql-lab-control-panel')
details = panel.get_attribute('labdetails')
details_json = json.loads(details)
gshell_link = details_json[0]['href']
email = details_json[1]['value']
password = details_json[2]['value']
print("[QL_GetAccount] Got a Google account, it is", (email, password, gshell_link))
return email, password, gshell_link, driver
# def get_google_account():
# from utils import global_vars
# import subprocess
# if not global_vars.TEST_ACCOUNT:
# print("[QL_GetAccount] Getting new google account..." )
# account = global_vars.ql_list.request_new_account()
# res = subprocess.check_output(['bash','-c', "src/qwiklabs/get_account.sh {} {} ".format(str(global_vars.PROXY), account)]).decode('utf-8')
# if res == 'False' or 'False' in res:
# res = bool(res)
# # Maybe if we failed there was a connection issue, so ping qwiklabs.com to check
# # If ping is successful the account is out of quota
# if global_vars.PROXY is False:
# r = requests.get("https://qwiklabs.com")
# else :
# proxyDict = {
# "socksVersion" : 5,
# "socks" : global_vars.PROXY
# }
# r = requests.get("https://qwiklabs.com", proxies=proxyDict)
# # ping was unsuccessful, delete the account
# if r.stats_code == 200:
# print("[QL_GetAccount] Something went wrong, trying again")
# return get_google_account()
# else:
# global_vars.account_list.mark_account_for_deletition(account)
# else:
# res = tuple(res.rsplit())
# print("[QL_GetGAccount] {}".format(res))
# return res
# else:
# print("[QL_GetAccount] Using Test Account {}".format(global_vars.TEST_ACCOUNT) )
# return global_vars.TEST_ACCOUNT

Binary file not shown.

View File

@ -19,4 +19,4 @@ fi
sleep 5
#Now start the actual bot, we're in a safe environment
DISPLAY=$d python src/qwiklabs/get_account.py $2
DISPLAY=:2 python src/qwiklabs/get_account.py $2

BIN
src/shell/__init__.pyc Normal file

Binary file not shown.

9
src/shell/gists/gist.py Normal file
View File

@ -0,0 +1,9 @@
from abc import ABCMeta, abstractmethod
from shell import shell
class Gist:
__metaclass__ = ABCMeta
@abstractmethod
def execute(self, Shell: shell):
pass

View File

@ -1,6 +1,8 @@
from enum import Enum
from shell.gists.userbot import UserBot
from shell import shell
class Gists(Enum):
PROXY1 = "https://colab.research.google.com/gist/EmaMaker/4e1478c9913a2df58fc1b8ff422fa161/proxy.ipynb"
PROXY2 = "https://colab.research.google.com/gist/EmaMaker/79645e7bcec4413ce868e3f9bc4d1939/proxy.ipynb"
MINER = "https://colab.research.google.com/gist/EmaMaker/296863713437f703ec1aa56ae45b1f8f/nicehash-miner.ipynb"
MINER = "https://colab.research.google.com/gist/EmaMaker/296863713437f703ec1aa56ae45b1f8f/nicehash-miner.ipynb"

View File

@ -0,0 +1,5 @@
#remove home to get rid of any remaining files from latest session. Only happens in test mode, but it's always useful
.2. rm -rf ~/*
.2. mkdir ~/.cloudshell
.2. touch ~/.cloudshell/no-pip-warning
.2. touch ~/.cloudshell/no-apt-get-warning

View File

@ -0,0 +1,7 @@
.30. sudo apt-get install -y xorg xserver-xephyr
.2.1
.2.1
.30.1
.45. sudo apt-get install -y firefox-esr tigervnc-standalone-server curlftpfs python3 python3-pip
# no view-only password
.2. clear

View File

@ -0,0 +1,7 @@
.10. python3 -m pip install --upgrade pip
.10. python3 -m pip install selenium
.15. wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
.45. sudo apt-get install -y ./google-chrome-stable_current_amd64.deb
.10. wget https://chromedriver.storage.googleapis.com/94.0.4606.61/chromedriver_linux64.zip
.2. unzip chromedriver_linux64.zip
.2. sudo mv chromedriver /usr/bin/

View File

@ -0,0 +1,5 @@
.5. vncserver :1 -geometry 1280x720
# insert password for vnc server
.2. hellogoodbye
.2. hellogoodbye
.5. n

View File

@ -0,0 +1,131 @@
'''Website User Simulator
Simulate the behaviour of a user visiting the website
* Start from a given page
* Fetch all redirection links present on the page withing the defines website scope
* Chose a random one and redirect to that
* Repeat until out of the website (link to another website) or maximum number of times exceeded
This requires the Tor Daemon to be installed, so you want to look into installing that. Most Linux Distros have that in their repos
This can also be used to simulate YT views:
* When visiting the webpage click on the play button
* Wait at least 30 seconds
* Youtube should count this as a view
'''
from selenium.webdriver.support.ui import Select
import selenium.webdriver as webdriver
import selenium.common.exceptions as sexceptions
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from time import sleep
import random
import subprocess
import sys
outside_website_scopes = ["blog.altervista.org", "it.altervista.org", "pinterest",
"facebook.com", "instagram.com", "iubenda.com", "twitter.com", "#"]
ACCEPT_COOKIES_BTN_SEL = ".iubenda-cs-accept-btn"
MAX_REDIRECTION = 5
LOOK_FOR_ADS = True
def visit(browser, url, redirs):
# go to the page
print(f"Visiting: {url}")
browser.get(url)
# Sleep a little bit to wait for all of the page to be loaded (especially ads) and to simulate a user reading. Keep in mind the profile is preset to accept cookies on this website
sleep(3)
click_noexit(browser, By.CSS_SELECTOR, ACCEPT_COOKIES_BTN_SEL, 10)
for i in range(0, MAX_REDIRECTION):
try:
sleep(4)
# Fetch all the element with links present on the page
all_redirect_elements = [x for x in browser.find_elements_by_xpath('.//a') if x.get_attribute('href') != None]
print(all_redirect_elements)
clickable_elements = [x for x in all_redirect_elements if x.is_enabled()]
if LOOK_FOR_ADS:
# Fetch all the ads present on the page. Not all iframes are ads, but that's a good way to get them
ads = browser.find_elements_by_tag_name("iframe")
else:
if not all_redirect_elements:
done(browser)
# Include wanted urls
# wanted_urls = [x for x in all_urls for a in website_scopes if x.find(a) != -1]
# Exclude unwanted urls
allowed_elements = []
for element in clickable_elements:
broke = False
for outside_scope in outside_website_scopes:
if outside_scope in element.get_attribute('href'):
broke = True
break
if not broke:
allowed_elements.append(element)
# Remove duplicates in list
allowed_elements = list(dict.fromkeys(allowed_elements))
page_index = random.randint(0, len(allowed_elements)-1)
if LOOK_FOR_ADS:
if(random.random()) < 0.15:
print("Moving toward", allowed_elements[page_index].get_attribute('href'))
allowed_elements[page_index].click()
else:
#visit ad
#get a random iframe and click it
ads[random.randint(0, len(ads)-1)].click()
sleep(5)
print("I'm out of the website, bye!")
break
done(browser)
else:
print("Moving toward", allowed_elements[page_index].get_attribute('href'))
allowed_elements[page_index].click()
except Exception as e:
print("Error, closing", e)
continue
def main():
browser = webdriver.Chrome(executable_path="/home/emamaker/Documents/Projects/GShellAutomator/chromedriver")
launch_browser(browser, sys.argv[1], False)
print("Script executed!")
def launch_browser(browser, url, yt):
if yt:
visit_yt(browser, url)
else:
visit(browser, url, 0)
def done(browser):
global tor
browser.close()
browser.quit()
def click_exit(browser, by, desc, timeout):
try:
WebDriverWait(browser, timeout).until(EC.element_to_be_clickable((by, desc))).click()
except:
done(browser)
def click_noexit(browser, by, desc, timeout):
try:
WebDriverWait(browser, timeout).until(EC.element_to_be_clickable((by, desc))).click()
except:
done(browser)
if __name__ == '__main__':
main()

View File

@ -0,0 +1,3 @@
#this is in its own custom script so that we can set a maximum timer
#this time is a large estimate of the actual time required, you never know
.60. DISPLAY=:1 python3 userbot.py https://giangillorossi.altervista.org

View File

@ -0,0 +1,35 @@
from shell.gists.gist import Gist
from shell.shell import Shell
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
class UserBot (Gist):
def execute(self, shell):
# prepare for userbot, without enabling ephimeral files will be there next reboot
shell.execute_script_from_local("./src/shell/gists/scripts/clear_homedir.sh")
shell.execute_script_from_local("./src/shell/gists/scripts/prepare_environment_ubuntu.sh")
i = 1
while True:
print("[UserBOT] Starting execution #{}".format(i))
i += 1
#clean home dir, just in case
shell.execute_script_from_local("./src/shell/gists/scripts/clear_homedir.sh")
#download bot
shell.execute_command("wget https://gist.githubusercontent.com/EmaMaker/86d49a9b6b2a66e6299ec4cd9fd8de12/raw/b26b9328a1ee06d34124eee86ff024ae92e0bbf8/userbot.py")
#prepare python
shell.execute_script_from_local("./src/shell/gists/scripts/prepare_python_environment.sh")
#the userbot.py script is executing chromedriver in headless mode, so vnc isn't actually needed
shell.execute_script_from_local("./src/shell/gists/scripts/start_vnc_server.sh")
# now execute userbot. Probably there will be some time between end of execution and start of reboot
shell.execute_script_from_local("./src/shell/gists/scripts/userbot_script.sh")
# reboot the machine to get a clean one with a new ip and mac
shell.reboot()

255
src/shell/shell.py Normal file
View File

@ -0,0 +1,255 @@
'''
Executes an arbitrary github gist on GCloudShell in ephimeral mode, no traces left in the docker containers
Every restart of the Google Cloud Shell the machine appears to be a completely different one (ip, mac)
Specs:
2x vCPU Intel Xeon @ 2.6 GHz
1GB Ram
'''
# text.get_attribute("value") return the text content of a text field
from qwiklabs.get_account import get_google_account
from utils import global_vars, browser_manager
import random
import time
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium import webdriver
class Shell():
def __init__(self):
print("[GCShell] Created a new GShell instance ")
Shell.TEXT_AREA_XPATH = '/html/body/cloud-shell-root/div/docked/stacked-layout/div[1]/div/devshell/terminal-container/div/xterm-terminal-tab/div/xterm-terminal/div/div/div/div[2]/div[1]/textarea'
Shell.XTERM_SCREEN_XPATH = '/html/body/cloud-shell-root/div/docked/stacked-layout/div[1]/devshell/terminal-container/div/xterm-terminal-tab/div/xterm-terminal/div/div/div/div[2]'
self.account_specs = get_google_account()
self.driver = self.account_specs[3]
self.account = (self.account_specs[0], self.account_specs[1])
self.gcs_link = self.account_specs[2]
self.started = False
def start(self):
if self.account_specs is False:
print("[GCShell] No account was available, can't start!")
return
# If using a test account, use the provided gsc link
if global_vars.TEST_ACCOUNT is False:
while (browser_manager.waitForElement(self.driver, By.XPATH, '/html/body/main/div/ql-drawer-container/ql-drawer-content/ql-drawer-container/ql-drawer[1]/ql-lab-control-panel') is False):
print("[GCShell] Page not ready, waiting...")
# get the shadow_dom element and click it
panel = self.driver.find_element_by_xpath('/html/body/main/div/ql-drawer-container/ql-drawer-content/ql-drawer-container/ql-drawer[1]/ql-lab-control-panel')
shadow1 = browser_manager.expand_shadow_element(self.driver, panel)
open_shell = shadow1.find_element_by_css_selector('div:nth-child(2) > div > ql-button')
print("[GCShell] Opening shell")
open_shell.click()
time.sleep(5)
# Now switch to the second tab
self.driver.switch_to.window(self.driver.window_handles[1]) #apparently clicking in selenium doesn't switch window
else:
print("[GCShell] Using Google Test Account")
self.driver.get(self.gcs_link)
time.sleep(5)
oldPage = False
#The email may be already filled in, just in case delete it and type it again
while browser_manager.waitForElement(self.driver, By.CSS_SELECTOR, '#identifierId') is False:
if browser_manager.waitForElement(self.driver, By.CSS_SELECTOR, '#Email') is not False:
oldPage = True
break
print("[GCShell] Page not ready, waiting...")
time.sleep(5)
# print("Got old page?", oldPage)
print("[GCShell] Inserting credentials")
if oldPage:
email = self.driver.find_element_by_css_selector('#Email')
email.send_keys(Keys.CONTROL, 'a')
email.send_keys(Keys.BACKSPACE)
browser_manager.inputText(self.driver, By.CSS_SELECTOR, '#Email', self.account[0])
browser_manager.clickButton(self.driver, By.CSS_SELECTOR, '#next')
time.sleep(5)
#password is always empty, no need to empty the textbox first
browser_manager.inputText(self.driver, By.CSS_SELECTOR, '#password', self.account[1])
browser_manager.clickButton(self.driver, By.CSS_SELECTOR, '#submit')
else:
email = self.driver.find_element_by_css_selector('#identifierId')
email.send_keys(Keys.CONTROL, 'a')
email.send_keys(Keys.BACKSPACE)
browser_manager.inputText(self.driver, By.CSS_SELECTOR, '#identifierId', self.account[0])
browser_manager.clickButton(self.driver, By.XPATH, '/html/body/div[1]/div[1]/div[2]/div/div[2]/div/div/div[2]/div/div[2]/div/div[1]/div/div/button')
time.sleep(5)
#password is always empty, no need to empty the textbox first
browser_manager.inputText(self.driver, By.XPATH, '//*[@id="password"]/div[1]/div/div[1]/input', self.account[1])
browser_manager.clickButton(self.driver, By.XPATH, '/html/body/div[1]/div[1]/div[2]/div/div[2]/div/div/div[2]/div/div[2]/div/div[1]/div/div/button')
# Login Done: Accept the EULA
browser_manager.clickButton(self.driver, By.XPATH, '//*[@id="accept"]')
time.sleep(5)
# Now we are in GCP. We need to accept some stuff (country, terms of use)
# The form accepts keyboard input and anticipates it proposing a country. Make up a random string, send it. It will be out country
# Select country button
#there's no country starting with an 'x'
alphabet = 'abcdefghijklmnopqrstuwyz'
letters = 3
country = ""
for i in range(0, letters):
country += alphabet[random.randint(0, len(alphabet)-1)]
#country += '\n'
# print(country)
browser_manager.inputText(self.driver, By.XPATH, '/html/body/div[3]/div[3]/div/mat-dialog-container/xap-deferred-loader-outlet/ng-component/mat-dialog-content/form/cfc-tos-checkboxes/form/div[1]/cfc-loader/div/mat-form-field/div/div[1]/div[3]/ace-select', country, interval=0, after_delay=2)
#accept terms of use
browser_manager.clickButton(self.driver, By.XPATH, '/html/body/div[3]/div[3]/div/mat-dialog-container/xap-deferred-loader-outlet/ng-component/mat-dialog-content/form/cfc-tos-checkboxes/form/div[2]/mat-checkbox/label/span[1]')
#finally agree
browser_manager.clickButton(self.driver, By.XPATH, '/html/body/div[3]/div[3]/div/mat-dialog-container/xap-deferred-loader-outlet/ng-component/mat-dialog-actions/ace-progress-button/div[1]/button')
#wait for the page to load
time.sleep(20)
# Now open GCS, clicking the button
browser_manager.clickButton(self.driver, By.XPATH, '//*[@id="pcc-devshell-container"]/xap-deferred-loader-outlet/pcc-platform-bar-devshell-button/pcc-platform-bar-button/button')
time.sleep(10)
# Sometimes another accept pops out
browser_manager.clickButton(self.driver, By.XPATH, '/html/body/div[3]/div[3]/div/mat-dialog-container/xap-deferred-loader-outlet/ng-component/mat-dialog-content/form/cfc-tos-checkboxes/form/div[1]/cfc-loader/div/mat-form-field/div/div[1]/div[3]/ace-select')
# Sometimes there's even an accept button inside the iframe, where the command area should be
self.switch_to_shell_iframe()
browser_manager.clickButton(self.driver, By.XPATH, '/html/body/div/div[2]/div/mat-dialog-container/dialog-overlay/div[5]/modal-action/button')
#wait for the machine to boot
time.sleep(20)
self.started = True
# Done. This is ours to command now!
# reboot to get a fresh machine (we are in ephimeral mode)
def reboot(self):
print("[GCShell] Going down for reboot NOW!")
self.switch_to_shell_iframe()
browser_manager.clickButton(self.driver, By.XPATH, '/html/body/cloud-shell-root/div/docked/stacked-layout/div[1]/devshell/devshell-toolbar/csh-header/mat-toolbar/csh-header-buttons/more-button/button')
browser_manager.clickButton(self.driver, By.XPATH, '/html/body/div[3]/div[2]/div/div/div/button[1]')
browser_manager.clickButton(self.driver, By.XPATH, '/html/body/div[3]/div[2]/div/mat-dialog-container/dialog-overlay/div[5]/modal-action[1]/button')
rebooted = False
t = 0
#wait for it to actually reboot
while not rebooted:
time.sleep(5)
t += 5
try:
self.get_text_area()
rebooted = True
except:
print("[GCShell]{} seconds passed, VM still hasn't fully rebooted".format(t))
print("[GCShell] VM fully rebooted")
def toggle_ephimeral(self):
print("[GCShell] Toggling ephimeral mode...")
#open menu
browser_manager.clickButton(self.driver, By.XPATH, '/html/body/cloud-shell-root/div/docked/stacked-layout/div[1]/devshell/devshell-toolbar/csh-header/mat-toolbar/csh-header-buttons/more-button/button')
#click ephimeral mode option
browser_manager.clickButton(self.driver, By.XPATH, '//*[@id="mat-menu-panel-10"]/div/button[6]')
#this only appears when enabling. Click to enable
browser_manager.clickButton(self.driver, By.XPATH, '//*[@id="mat-slide-toggle-1"]')
#This confirms enabling or disabling, depending if it's already enabled or not
browser_manager.clickButton(self.driver, By.XPATH, '//*[@id="mat-dialog-0"]/dialog-overlay/div[5]/modal-action[1]/button')
print("[GCShell] Toggled ephimeral mode...")
# select the text area. This is where we can input commands
# (assumes the window is still open)
def switch_to_shell_iframe(self):
#sometimes we're still in the iframe, so switching to main content is needed
self.driver.switch_to.default_content()
#switch to shell iframe
frame = self.driver.find_element_by_xpath('/html/body/pan-shell/pcc-shell/cfc-panel-container/div/div/cfc-panel[1]/div[1]/div/div[3]/cfc-panel-container/div/div/cfc-panel/div/div/cfc-panel-container/div/div/cfc-panel[2]/div/div[1]/pcc-cloud-shell-wrapper/xap-deferred-loader-outlet/pcc-cloud-shell/div/div[2]/iframe')
self.driver.switch_to.frame(frame)
return frame
def get_text_area(self):
if self.started:
self.switch_to_shell_iframe()
return self.driver.find_element_by_xpath(Shell.TEXT_AREA_XPATH)
return False
def execute_command(self, command, stop_current=False):
text_area = self.get_text_area()
if text_area is False:
print("[GCShell] No account were available, can't execute cmd!")
return
if stop_current:
text_area.send_keys(Keys.CONTROL, 'c')
time.sleep(5) #wait in case it takes a bit of time to stop
browser_manager.inputText_no_find(self.driver, text_area, command + '\n', interval=0)
def execute_python_custom_script(self, gist):
gist.execute(self)
# we use our own "language" to add information to the script (e.g. how much to wait before a command is complete)
def execute_script_from_local(self, gist_path):
print("[GCShell] Executing gist from", gist_path)
with open(gist_path, "r") as file:
for line in file.readlines():
line = line.strip().rstrip()
print("[GCShell] Executing specific line:", line)
# that's a comment, ignore by skipping to the next line
if( line.startswith("#") ):
print("[GCShell] Line was a comment, skipping")
else:
i1 = line.find('.')
i2 = line.find('.',1)
sleeptime = int(line[i1+1:i2])
command = line[i2+1:]
print("[GCShell] Executing command:", line, "for", sleeptime, "seconds")
self.execute_command(command, False)
time.sleep(sleeptime)
# take a raw text file from the url (github, pastebin possibly), run it using curl
def execute_raw_gist(self, gist_url, arguments):
arguments_str = ""
for a in arguments:
arguments_str += " "
arguments_str += a
command = gist_url + " " + arguments_str
self.execute_command(command, stop_current=True)

BIN
src/shell/shell.pyc Normal file

Binary file not shown.

BIN
src/utils/__init__.pyc Normal file

Binary file not shown.

View File

@ -25,7 +25,7 @@ def waitForElement(browser, by, selector, timeout=5, after_delay=0):
except:
return False
def clickButton(browser, by, selector, timeout=5, after_delay=1.5):
def clickButton(browser, by, selector, timeout=7, after_delay=2):
try:
WebDriverWait(browser, timeout).until(EC.element_to_be_clickable((by, selector))).click()
@ -33,7 +33,7 @@ def clickButton(browser, by, selector, timeout=5, after_delay=1.5):
except:
return False
def inputText(browser, by, selector, text, timeout=5, interval=0.2, after_delay=0):
def inputText(browser, by, selector, text, timeout=5, interval=0.2, after_delay=2):
try:
element = WebDriverWait(browser, timeout).until(EC.element_to_be_clickable((by, selector)))
for i in text:
@ -44,6 +44,16 @@ def inputText(browser, by, selector, text, timeout=5, interval=0.2, after_delay=
except:
return False
def inputText_no_find(browser, element, text, timeout=5, interval=0.2, after_delay=2):
try:
for i in text:
element.send_keys(i)
time.sleep(interval)
time.sleep(after_delay)
except:
return False
# A lifesaver: https://stackoverflow.com/questions/36141681/does-anybody-know-how-to-identify-shadow-dom-web-elements-using-selenium-webdriv
def select_shadow_element_by_css_selector(browser, selector):
@ -71,7 +81,7 @@ def start_browser(headless=False):
# #Set Chromedriver Options
opts = webdriver.ChromeOptions()
if headless is True:
options.headless = True
opts.headless = True
opts.add_argument('--enable-javascript') #enabling javascript is needed in order to not get recognized as a bot
user_agent = random_user_agent()
@ -82,7 +92,7 @@ def start_browser(headless=False):
print("[Browser_Manager] Starting new browser. Proxy: {} | UserAgent {}".format(PROXY, user_agent))
#Fire up chromedriver
chromedriver = webdriver.Chrome(options=opts)
chromedriver = webdriver.Chrome(executable_path='./chromedriver', options=opts)
time.sleep(3)
return chromedriver

View File

@ -3,4 +3,8 @@ from qwiklabs import account_list
PROXY = False
ql_list = account_list.QL_AccountList('/home/emamaker/Documents/Projects/GColabAutomator/GColabAutomator-v2/src/qwiklabs_available_accounts.txt')
TEST_ACCOUNT = False
# TEST_ACCOUNT = ('student-03-8b9257f4e203@qwiklabs.net', '925ZwgHmPf')
# email, password, gcloud shell link
# TEST_ACCOUNT = ('student-01-474fc01a4ea5@qwiklabs.net', 'PkR9NJR4ms5D', 'https://accounts.google.com/AddSession?service=accountsettings&sarp=1&continue=https%3A%2F%2Fconsole.cloud.google.com%2Fhome%2Fdashboard%3Fproject%3Dqwiklabs-gcp-03-676b378e29e0#Email=student-01-474fc01a4ea5@qwiklabs.net')
TEST_QWIKLABS_ACCOUNT = False
# TEST_QWIKLABS_ACCOUNT = "i3z8qtab@xojxe.com"

BIN
src/utils/global_vars.pyc Normal file

Binary file not shown.

View File

@ -1,55 +0,0 @@
from ngrok import ngrok
from colab import colab
from colab.gists import Gists
from utils import global_vars
from qwiklabs import get_account
import time
class Proxy():
def __init__(self):
Proxy.PROXY_COMBOS = [
( ('giangillo.rossi1@gmail.com', 'emamaker02'), Gists.PROXY1.value),
( ('giangillo.rossi2@gmail.com', 'emamaker02'), Gists.PROXY2.value)
]
Proxy.TIME_ELAPSED = 2400 #time that has to pass before changing account combo, in seconds
self.account_combo_index = 0
self.account_combo = Proxy.PROXY_COMBOS[self.account_combo_index]
self.last_time = time.time()
print("[Proxy] First time running, starting a new proxy session")
self.start_new_proxy_session()
def request_new_proxy(self):
print("[PROXY] A new proxy has been requested")
# then it's time to close the current gist+ngrok account and open another one
if time.time() - self.last_time > Proxy.TIME_ELAPSED:
print("[PROXY] Time for current account {} has expired".format(self.account_combo[0]))
self.ngrok.close_browser()
self.colab.quit()
# it's not needed to manually close the colab session, just leave it decay on it's own
# but it's needed to close ngrok
self.account_combo_index = (self.account_combo_index+1) % len(Proxy.PROXY_COMBOS)
self.account_combo = Proxy.PROXY_COMBOS[self.account_combo_index]
print("[PROXY] Switching to new account {}".format(self.account_combo[1]))
self.start_new_proxy_session()
else:
print("[PROXY] Refreshing existing proxy to get new ip")
#just refresh
self.colab.refresh_event.set()
time.sleep(90)
global_vars.PROXY = self.ngrok.get_proxy_ip()
print("[PROXY] Done! New proxy is: {} ".format(global_vars.PROXY))
def start_new_proxy_session(self):
self.colab = colab.ColabGist(self.account_combo[1], get_account.get_google_account())
self.colab.run() #this launches a new thread
time.sleep(90)
self.ngrok = ngrok.Ngrok( self.account_combo[0] )