Initial import
authorAntonio Ospite <ospite@studenti.unina.it>
Tue, 2 Apr 2013 16:12:11 +0000 (18:12 +0200)
committerAntonio Ospite <ospite@studenti.unina.it>
Tue, 2 Apr 2013 16:19:30 +0000 (18:19 +0200)
README.txt [new file with mode: 0644]
crackpop.py [new file with mode: 0755]

diff --git a/README.txt b/README.txt
new file mode 100644 (file)
index 0000000..e764d18
--- /dev/null
@@ -0,0 +1,56 @@
+If you forgot the password of your pop3 account but you remember the rule or
+the pattern you used to create it, crackpop can help you to recover the
+password.
+
+Just build up a regular expression that you think matches the password and
+crackpop will try to generate all the strings matchable by the regular
+expression and try to access your pop3 account with them.
+
+As an example let's try to hack Randall Munroe's account:
+
+  $ ./crackpop.py \
+      --host popmail.xkcd.example.net \
+      --user randall \
+      --pattern "[Cc]orrect [Hh]orse [Bb]attery [Ss]taple"
+
+
+DEPENDENCIES
+
+crackpop uses the 'exrex' python module, in case it is not packaged for your
+system you can get it with 'pip'; example for Debian based systems:
+
+  $ sudo aptitude install python-pip
+  $ sudo pip install exrex
+
+
+NOTE
+
+The program always prints out the error status on failed authentication
+attempts in order to let the user know what is going on.
+
+This is because some POP3 servers may be more creative than others, and
+sometimes it is possible to differentiate between authentication events only
+by looking at the error message.
+
+For instance popmail.libero.it is quite weird, this is what happens:
+
+  - When the username is wrong the server replies with and expected:
+      
+      -ERR [AUTH] invalid user or password
+      
+  - When the username is right but the password is wrong the server replies
+    with an information-leaking:
+
+      -ERR ERROR 119 invalid user or password err 30 
+
+  - When the password is right the server replies with:
+
+      -ERR [AUTH] POP3 access not allowed
+
+    because it does not allow POP3 operations from users on networks different
+    from its own (but it still allows _connections_ from other networks tho).
+
+Yes, with pomail.libero.it it is possible to differentiate the "invalid user"
+case from the "invalid password" one.
+
+Have fun!
diff --git a/crackpop.py b/crackpop.py
new file mode 100755 (executable)
index 0000000..3c7d0f7
--- /dev/null
@@ -0,0 +1,102 @@
+#!/usr/bin/env python
+#
+# crackpop - a pattern-generated-dictionary pop3 password cracker
+#
+# Copyright (C) 2013  Antonio Ospite <ospite@studenti.unina.it>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import argparse
+import exrex
+import poplib
+
+__description = "crackpop is a pattern-generated-dictionary pop3 password cracker"
+__version = "0.1"
+__author_info = "Antonio Ospite"
+
+
+def generate_passwords(password_pattern, dry_run=False):
+    passwords = list(exrex.generate(password_pattern))
+
+    if dry_run:
+        print "Generated %d passwords." % len(passwords)
+        for p in passwords:
+            print p
+        return []
+
+    return passwords
+
+
+def crackpop(host, port, user, passwords):
+    print "Testing %d passwords." % len(passwords)
+    for p in passwords:
+        # TODO maybe the same connection can be reused for more than one try,
+        # but some logic needs to be added to detect the maximum allowed
+        # authentication attempts or a disconnection from the server.
+        pop3 = poplib.POP3(host, port)
+        try:
+            pop3.user(user)
+            pop3.pass_(p)
+        except Exception, e:
+            print e.message, "(password: %s)" % p
+            del pop3
+            continue
+        else:
+            print "Found! (password: %s)" % p
+            break
+
+
+def option_parser():
+    usage = "usage: %(prog)s [options]"
+
+    parser = argparse.ArgumentParser(
+        usage=usage,
+        description=__description,
+        epilog=__author_info,
+        version='%(prog)s ' + __version)
+
+    parser.add_argument(
+        '-H', '--host',  metavar="<host>",
+        dest='host', required=True,
+        help='the host where the pop3 server is')
+
+    parser.add_argument(
+        '-P', '--port', metavar="<port>",
+        dest='port', default=110,
+        help='the port the pop3 server is listening on')
+
+    parser.add_argument(
+        '-u', '--user', metavar="<user>",
+        dest='user', required=True,
+        help='username of the pop3 account')
+
+    parser.add_argument(
+        '-p', '--pattern', metavar="<password_pattern>",
+        dest='password_pattern', required=True,
+        help='the regular expression describing the pattern of the password')
+
+    parser.add_argument(
+        '-d', '--dry-run',
+        dest='dry_run', action='store_const', const=True,
+        help='only print out the passwords, do not connect to the pop3 server')
+
+    return parser
+
+
+if __name__ == "__main__":
+    parser = option_parser()
+    args = parser.parse_args()
+
+    passwords = generate_passwords(args.password_pattern, args.dry_run)
+    crackpop(args.host, args.port, args.user, passwords)