├── readme pics
├── webdev.jpg
├── response.jpg
└── firevueout.jpg
├── .gitignore
├── README.md
└── firevue.py
/readme pics/webdev.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lightflix/firevue/HEAD/readme pics/webdev.jpg
--------------------------------------------------------------------------------
/readme pics/response.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lightflix/firevue/HEAD/readme pics/response.jpg
--------------------------------------------------------------------------------
/readme pics/firevueout.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lightflix/firevue/HEAD/readme pics/firevueout.jpg
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | pip-wheel-metadata/
24 | share/python-wheels/
25 | *.egg-info/
26 | .installed.cfg
27 | *.egg
28 | MANIFEST
29 |
30 | # PyInstaller
31 | # Usually these files are written by a python script from a template
32 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
33 | *.manifest
34 | *.spec
35 |
36 | # Installer logs
37 | pip-log.txt
38 | pip-delete-this-directory.txt
39 |
40 | # Unit test / coverage reports
41 | htmlcov/
42 | .tox/
43 | .nox/
44 | .coverage
45 | .coverage.*
46 | .cache
47 | nosetests.xml
48 | coverage.xml
49 | *.cover
50 | *.py,cover
51 | .hypothesis/
52 | .pytest_cache/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | target/
76 |
77 | # Jupyter Notebook
78 | .ipynb_checkpoints
79 |
80 | # IPython
81 | profile_default/
82 | ipython_config.py
83 |
84 | # pyenv
85 | .python-version
86 |
87 | # pipenv
88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
91 | # install all needed dependencies.
92 | #Pipfile.lock
93 |
94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
95 | __pypackages__/
96 |
97 | # Celery stuff
98 | celerybeat-schedule
99 | celerybeat.pid
100 |
101 | # SageMath parsed files
102 | *.sage.py
103 |
104 | # Environments
105 | .env
106 | .venv
107 | env/
108 | venv/
109 | ENV/
110 | env.bak/
111 | venv.bak/
112 |
113 | # Spyder project settings
114 | .spyderproject
115 | .spyproject
116 |
117 | # Rope project settings
118 | .ropeproject
119 |
120 | # mkdocs documentation
121 | /site
122 |
123 | # mypy
124 | .mypy_cache/
125 | .dmypy.json
126 | dmypy.json
127 |
128 | # Pyre type checker
129 | .pyre/
130 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Firevue
2 | A hirevue question cracker
3 |
4 | ## *Update (15-11-2023): Many users reported that this script does not work anymore. Hirevue may have likely patched this.*
5 |
6 | ### Requirements
7 | 1. Python 3
8 | 2. Mozilla Firefox (Google Chrome might work but I haven't tested it).
9 | 3. Hirevue interview invite link
10 |
11 | ### Steps to run this code
12 | 1. Download the repository or the python file `firevue.py`.
13 | 2. Move the python file in any new directory (folder). This could be anywhere on the system.
14 | 3. Open the valid hirevue invite link in Firefox. If you don't see your name on the page, click the continue or start button and the page showing your name should be next. This will not the start the interview. But it's an important step in capturing the questions.
15 | 4. Open the web development console (`CTRL+SHIFT+I` or `CMD+ALT+I`). Click on the network tab.
16 | 5. Reload the current page so it can capture all web traffic.
17 | 6. Right click on any entry in the console and save all as HAR. Save the HAR file into the same folder that contains the python script `firevue.py`.
18 |
19 |
20 | 7. On Windows, press (Windows key + R) and type `cmd`, then hit enter. This will open a black command prompt window. For Linux/MacOS, open the terminal.
21 | 8. In the terminal, type `cd`, followed by a space.
22 | 9. On Windows/macOS, drag the folder onto the command prompt window you just opened. You'll see the name of that folder appear. The command now should look like `cd C:\the\path\to\the\folder`. Hit enter.
23 | 10. Type `python3 firevue.py`, then hit enter. If "python3" is not a recognized command then either 1) run the command "python" or 2) go to where python is installed and either 1) rename "python.exe" to "python3.exe" or 2) copy and paste python.exe into the same directory and rename the copy "python3.exe"
24 | 11. The script should automatically detect the HAR file and try to do its magic. Follow the instructions on-screen. Eventually it will display your interview questions like shown below:
25 |
26 |
27 | 12. Optional: If you want to manually decode encoded text that you find in the HAR file, you can use the `-d` option. Use `python3 firevue.py -h` or `python3 firevue.py --help` to check usage.
28 |
29 | ### FAQs
30 |
31 | #### 1. What does this script do and how does it work?
32 | This script reads web responses that were sent by Hirevue after you clicked the interview link. To my surprise, Hirevue sends all interview-related data including questions to your browser before the interview even starts. This means our browser already has the questions beforehand and displays them one at a time as you go through the interview process. The questions though were encoded with some trivial algorithm which I could easily reverse engineer.
33 |
34 |
35 |
36 | I exploited this flaw and wrote python code to analyse the response, decrypt the "encrypted" questions and reveal them in plaintext so you can take all the time you need to prepare and give your best answers.
37 |
38 | #### 2. I don't have Python installed. How do I install it?
39 | - Windows users: https://www.tutorialspoint.com/how-to-install-python-in-windows
40 | - MacOS users: It's preinstalled. Just run the code using the above steps.
41 | - Linux users: Yes
42 |
43 | #### 3. I followed the above steps and it still doesn't work.
44 | There are a multitude of reasons why this may not work. Each interview can be slightly different in the way they're structured in the delivery process which could mean the questions may be in a different location in the received response, or the encoding might be different, or even worse - Hirevue may have fixed this. The only way I could have made my code more compatible was if I could test this on multiple interview links from different interviewers/companies. This isn't possible at the moment unless applicants send me their interview links for testing.
45 |
46 | #### 4. Why didn't I use the python requests library to automate all of this from just the interview link?
47 | I didn't want to access the interview page hundreds of times just to test my code, have them think I'm sus and increase my chances of failing the interview.
48 |
49 | #### 5. Is this safe to run?
50 | Yes, this script is 100% safe, runs locally and only reads the HAR file you've placed in its directory. You may be violating Hirevue's terms and conditions. I haven't read the fine print so I can't tell.
51 |
--------------------------------------------------------------------------------
/firevue.py:
--------------------------------------------------------------------------------
1 | #Simple tool to extract and decode hirevue questions from a browser HAR file
2 | import json, glob, argparse
3 |
4 | class color:
5 | BLUE = '\033[94m'
6 | GREEN = '\033[92m'
7 | YELLOW = '\033[93m'
8 | BOLD = '\033[1m'
9 | END = '\033[0m'
10 |
11 | def decoder_plaintext(ciphertext):
12 | #uno reverse card
13 | return ciphertext
14 |
15 | def decoder_original(ciphertext):
16 | #if questions couldn't be extracted
17 | if ciphertext == None:
18 | return ciphertext
19 |
20 | #convert string to character array
21 | chars = list(ciphertext)
22 |
23 | #first half of ciphertext array
24 | first_half = chars[:len(chars)//2]
25 |
26 | #second half of ciphertext array
27 | second_half = chars[len(chars)//2:]
28 | result = []
29 |
30 | for i in range(0, len(first_half)):
31 |
32 | #take one char from the first half and another char from the second half and add to resultant array.
33 | result.append(second_half[i])
34 | result.append(first_half[i])
35 |
36 | #if length of string is odd, it can't be evenly divided leaving out an additional character in the second half from being printed in loop, so we append that separately.
37 | if(len(chars) % 2 == 1):
38 | result.append(second_half[-1])
39 |
40 | #convert array/list back to string and return.
41 | return "".join(result)
42 |
43 | def harParse():
44 |
45 | har_file = glob.glob('*.har')
46 |
47 | if len(har_file) != 1:
48 | raise ValueError("[x] Error: HAR file does not exist in current directory or there is more than one HAR file in current directory!")
49 |
50 | #pick the only har file in current directory.
51 | har_filename = har_file[0]
52 |
53 | print(har_file[0])
54 |
55 | #load json from the HAR file
56 | har_file = json.load(open(har_filename, encoding="utf-8"))
57 |
58 | try:
59 | entries = har_file.get('log').get('entries')
60 | except AttributeError:
61 | raise AttributeError("[x] Error: Logs or Entries not found")
62 |
63 | #iterate though each request made by the browser
64 | try:
65 | for entry in entries:
66 |
67 | #in each entry, get inside the response object and then into content.
68 | content = entry.get('response').get('content')
69 |
70 | #questions are normally in the content object with the key "text"
71 | if "text" in content and "\"questions\":" in content.get("text") and "interviewerId" in content.get("text"):
72 | if not content.get("mimeType") == "application/json":
73 | print("[-] Warning: json mimetype not found, attempting to fetch info")
74 |
75 | #the questions are again encoded in json to pythonise that.
76 | return json.loads(content["text"])
77 |
78 | except TypeError:
79 | raise TypeError("[x] Error: Entries object isn't an iterable."+" Current Type:"+str(type(entries)))
80 |
81 | #no questions found :(
82 | return False
83 |
84 | def getQuestions(obj, decoder_func):
85 |
86 | question_list = obj.get('questions')
87 | if question_list == None:
88 | print("[x] Not available")
89 | else:
90 |
91 | i = 0
92 | for q in question_list:
93 | if q.get('type') != "mindx-assessment":
94 | print(color.GREEN+"\n - Question "+str(i+1)+": Prep Time (sec): "+str(q.get('prepTimeSeconds'))+", Max Answer Duration (sec): "+str(q.get('maxDuration'))+", Answer type: "+str(q.get('type'))+color.END+"\n")
95 |
96 | # send each question into the decoder that converts it back to plaintext.
97 | print(decoder_func(q.get('text')))
98 |
99 | #if questions is of multiple choice type, print options.
100 | if str(q.get('type')) == "multiple-selection":
101 | for option in q.get('options'):
102 | print(" [ ]",decoder_func(option))
103 |
104 | if str(q.get('type')) == "multiple-choice":
105 | for option in q.get('options'):
106 | print(" [ ]",decoder_func(option))
107 |
108 | print("\n -------------------------------")
109 | i += 1
110 |
111 | def getInfo(content, decoder_func):
112 |
113 | print(color.BOLD+"\n[-] Interview Details\n"+color.END)
114 |
115 | print(color.GREEN+" - Interviewer: "+color.END+str(content.get('interviewer')))
116 | print(color.GREEN+" - Role: "+color.END+str(content.get('position')))
117 | # print(color.GREEN+" - Total number of questions (includes games section): "+color.END+str(content.get('questionCount')))
118 | print(color.GREEN+" - Number of retries allowed: "+color.END+str(content.get('retryAllowance')))
119 | print(color.GREEN+" - Interview Duration: "+color.END+str(content.get('interviewDurationMinutes'))+" mins")
120 | print(color.GREEN+" - Estimated Duration: "+color.END+str(content.get('estimatedMinutesToComplete'))+" mins")
121 | print(color.GREEN+" - Invite Date: "+color.END+str(content.get('interviewUses')[0].get('invitedDate')))
122 |
123 | print(color.BOLD+"\n[-] All possible Interview Questions"+color.END)
124 | getQuestions(content, decoder_func)
125 |
126 | print(color.BOLD+"\n[-] Interview Sections"+color.END)
127 | section_list = content.get('sections')
128 | if section_list == None:
129 | print("[x] Not available")
130 | else:
131 | print("[-] Questions in this interview: ")
132 | i = 0
133 | for s in section_list:
134 | print(color.BLUE+"\n - Section "+str(i+1)+": "+str(s.get('name'))+color.END+"\n"+str(s.get('instructions'))+"\n\n -------------------------------")
135 | getQuestions(s, decoder_func)
136 | i += 1
137 |
138 | if __name__ == "__main__":
139 |
140 | cmdlineParse = argparse.ArgumentParser(description="This utility scans through a HAR file (JSON), automatically searches for interview data and displays them in decoded form. This is the default behaviour if no optional argument is specified. See below for manual decoding of text (for advanced users only).")
141 |
142 | cmdlineParse.add_argument('-d','--decode', type=str, help="Specify encoded text from the HAR file to be decoded, within double quotes. This mode does not read files and only uses the decode function of this script. Warning: Decoding may be incorrect if string supplied is incomplete or there are additional characters. \n\nExample usage: \npython firevue.py -d \"[String including \\\" as seen in file]\"", dest="encoded_string")
143 | args = cmdlineParse.parse_args()
144 |
145 | if args.encoded_string:
146 | print(color.YELLOW+"\n--== Firevue: Decode Mode ==--"+color.END)
147 |
148 | es = args.encoded_string
149 |
150 | if(es[:1] != "\"" or es[-1:] != "\""):
151 | print("[x] Error: Decoding failed. String incomplete or there are additional characters. String must begin and end with \\\" inside double quotes.\n")
152 | exit(0)
153 |
154 | print("\n"+decoder_original(es[1:-1])+"\n")
155 | exit(0)
156 |
157 | #parse result contains the interview details and all questions.
158 | try:
159 | parse_result = harParse()
160 | except ValueError as e:
161 | print(str(e))
162 | exit()
163 |
164 | if parse_result == False:
165 | print("\n[x] ERROR: Incorrect HAR parsing, exiting.")
166 | exit()
167 |
168 | print(color.YELLOW+"\n--== Firevue: A Hirevue Question Cracker v1.1 ==--"+color.END)
169 |
170 | #fetch values and print them neatly
171 | decoders = [decoder_plaintext, decoder_original]
172 | for decoder in decoders:
173 | getInfo(parse_result, decoder)
174 | print("\n\n*** If the questions are readable, enter \"Y\" to finish, otherwise enter \"N\" to try again. ***\n")
175 | response = input().lower()
176 | if response == "y":
177 | break
178 |
179 | # getInfo(parse_result)
180 |
181 | # with open("parsed_result.json","w") as f:
182 | # json.dump(parse_result, f, indent=4)
183 |
184 | print(color.BLUE+"\n[-] Good luck! :D\n"+color.END)
185 |
--------------------------------------------------------------------------------