0

**For my cybersecurity lab, I have to write a python program to iterate through a .txt file of passwords and a .txt file of usernames and check them against a Login.pyc file (that takes as its arguments), returning "Login successful.' if the matchup is correct. **

I'm trying to use the subprocess module to run python3 Login.pyc <user> <password> on the command line for each iteration. The passwords file has 100,000 passwords on it, so it is obviously taking extremely long.

import subprocess

if __name__ == "__main__":

  gang = open("gang")               #usernames .txt file (20 lines)
  pws = open("PwnedPWs100k")        #passwords .txt file  (100,000 lines)

  for name in gang:
    print("USER = ", name)
    
    for item in pws:
      
      output = subprocess.run(["python3", "Login.pyc", "{}".format(name.rstrip()), "{}".format(item.rstrip())], capture_output=True, text=True).stdout.strip("\n")
      
      if output == 'Login successful.':
        print("USER:" + name + "PASSWORD:" +item)
        break

   pws.close()
   gang.close()

UPDATE: I'm now importing the Login() function from Login.pyc, and since it uses command line arguments, I'm updating the sys.argv variables in nested loops and passing them against the Login() function. It is taking a similarly long time as my previous approach, however.

if __name__ == "__main__":

  with open("gang") as gang:
    with open("PwnedPWs100k") as pws:
      sys.argv = [sys.argv[0], 1, 2]

      for name in gang:
        sys.argv[1] = name.rstrip()

        for item in pws:
          sys.argv[2] = item.rstrip()

          while not Login.Login():
            continue
          else:
            print("USER:" + name + "PASSWORD:" +item)
            break
'''

Any ideas on optimizing the runtime?
jay
  • 3
  • 2
  • There are ways like using asyncio https://docs.python.org/3/library/asyncio-subprocess.html or a ThreadPoolExecutor https://docs.python.org/3/library/concurrent.futures.html – Michael Butscher Mar 06 '23 at 01:05

1 Answers1

1

Subprocess and multiprocessing are fine modules. But they don't seem like the right tool for this job.

The overhead of starting a new python interpreter and doing some imports and function definitions is killing you. No need to do that more than a few times.

I cannot simply import a Login() function because it is encrypted.

I reject that premise. Twice.

When you said "encrypted", I believe you mean the source text has been compiled to bytecode. That is not an impediment.

Just import Login, and look around with dir(Login) (or even help(Login) ). I bet a function like check_password leaps out at you. Call it. Two million times. In the same process.

Additionally, dis.dis() is available to you. So you can see what the "if name is main:" guard ran, and so you can see what happens within check_password. It offers you enough information to write your own source code that performs the same check. Which I'm pretty sure is the insight your professor was hoping you would eventually arrive at.

https://docs.python.org/3/library/dis.html


BTW, who ever writes things like gang.close() these days? Use a with open... context manager, already.


There's more than one library that supports import and similar operations. Look around on pypi. There's a whole ecosystem, which includes the standard importlib, devoted to making sense of binary bytecode artifacts.

Notice that you might need to use the same python interpreter version when generating and when consuming the .pyc file. There's a limited number to try: 3.8, 3.9, some others, it won't take long.


Maybe things don't go completely smoothly the first time out. Don't worry. Think like an attacker. You are Mallet.

Construct some source code that resembles an imagined Login.py. Convert it to .pyc bytecode. Pretend you can no longer see the source. Exploit the bytecode.

If it didn't quite go to your liking, adjust the source, produce another .pyc, lather, repeat, until you can confidently navigate through these waters.

J_H
  • 17,926
  • 4
  • 24
  • 44
  • You're right, I've just found in a subfolder an uncompiled version of Login.pyc, which does have a Login() function. However, the Login() function takes no arguments, and instead runs in the main function using sys.argv[1] as the Username and sys.argv[2] as the password. Any idea how I'd call this function with arguments after I import it? Thanks for your help! – jay Mar 06 '23 at 07:19
  • When you run a script, `sys.argv[]` is initialized with whatever optional arguments you supplied at the command line. But you are certainly free to stuff new values into `argv` each time you call a function. For example: `sys.argv = (sys.argv[0], username, candidate_password); print(Login())`. _And_ you can do that within a `for` loop. – J_H Mar 06 '23 at 08:47
  • Figured it out, and now I'm updating sys.argv[1] and sys.argv[2] in the nested loops, and the Login() function returns 1 if successful. However, calling the Login.Login() call is also taking forever to make 2 million calls. Any ideas? I get it if Login() really is the bottlenecking factor at this point. – jay Mar 06 '23 at 10:14
  • That might be as good as it gets. However, suppose that your professor was annoying and deliberately put a "[do_nothing() for i in range(1_000_000)]" loop in there, or equivalently a "time.sleep(1)" delay. Go look at the dis.dis(some_function) output to find what it does, and create your own source code which does it better. – J_H Mar 06 '23 at 15:18