├── .github └── workflows │ └── fame_the_name.yml ├── LICENSE ├── README.md ├── asyncrat_config_parser ├── README.md ├── asyncrat_config_parser.py ├── asyncrat_derive_and_decrypt_cyberchef.json └── requirements.txt ├── disassembly_in_the_d4rk ├── 1_splinter_cell │ └── README.md └── README.md ├── hacking_weaponizing_solitaire ├── README.md ├── card_generator │ ├── card_generator.py │ └── requirements.txt ├── cards_dll_proxy │ ├── cards.def │ └── cards_proxy.cpp └── example_files │ ├── 0_C.bmp │ ├── A_H.bmp │ ├── A_S.bmp │ ├── D_C.bmp │ ├── E_D.bmp │ ├── F_D.bmp │ ├── F_H.bmp │ ├── F_S.bmp │ ├── J_C.bmp │ ├── L_C.bmp │ ├── L_S.bmp │ ├── R_H.bmp │ ├── S_H.bmp │ ├── T_D.bmp │ ├── cards.rc │ ├── cards.res │ └── cards_weaponized.dll ├── master0Fnone_classes ├── 1_x86_Demystified │ ├── README.md │ ├── crackme │ │ ├── Wall_of_Fame.md │ │ └── crackme.zip │ ├── practice │ │ ├── practice.c │ │ ├── practice.zip │ │ ├── practice_assembly.s │ │ ├── practice_compiled.o │ │ ├── practice_machine_code.dmp │ │ └── practice_preprocessed.c │ ├── x86_assembly_reversing_cheat_sheet.drawio │ └── x86_assembly_reversing_cheat_sheet.pdf ├── 2_Sandbox_in_a_Box │ ├── README.md │ ├── Tools_and_Resources.md │ ├── crackme │ │ ├── Wall_of_Fame.md │ │ └── crackme.zip │ ├── parse_hashes.sh │ └── toggle_netplan.sh └── README.md ├── rct_full_res ├── README.md ├── rct_patch.exe └── rct_patch.py ├── rct_horror_mod ├── .gitignore ├── LICENSE ├── README.md ├── __init__.py ├── rct_horror_mod.py ├── requirements.txt ├── structs │ ├── __init__.py │ ├── bitmap.py │ ├── bitmapinfoheader.py │ ├── rgbquad.py │ ├── tgraphicrecord.py │ └── windows_process.py └── utils │ ├── __init__.py │ ├── bitmap_extract.py │ ├── dib_palette_dump.py │ ├── process_dumper.py │ └── rct_horror_patcher.py └── simple_transaction_classifier └── ML_Simple_Transaction_Classifier.ipynb /.github/workflows/fame_the_name.yml: -------------------------------------------------------------------------------- 1 | name: Fame the Name 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | directory: 6 | description: "Directory of the Wall of Fame file" 7 | required: true 8 | name: 9 | description: "Name to add" 10 | required: true 11 | date: 12 | description: "Date of entry" 13 | required: true 14 | 15 | env: 16 | TARGET_FILE: "Wall_of_Fame.md" 17 | TARGET_DIR: ${{ github.event.inputs.directory }} 18 | 19 | jobs: 20 | run: 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@v3.3.0 24 | with: 25 | repository: ${{ github.event.pull_request.head.repo.full_name }} 26 | ref: ${{ github.event.pull_request.head.ref }} 27 | 28 | - name: Add name to wall 29 | run: "sed -i '$i${{ github.event.inputs.name }} - ${{ github.event.inputs.date }}' '${{ env.TARGET_DIR }}/${{ env.TARGET_FILE }}'" 30 | 31 | - name: Commit changes 32 | uses: EndBug/add-and-commit@v9.1.1 33 | with: 34 | author_name: wall_of_fame_bot 35 | author_email: 8444166+jeFF0Falltrades@users.noreply.github.com 36 | message: "Automated Wall of Fame Update" 37 | add: "${{ env.TARGET_DIR }}/${{ env.TARGET_FILE }}" 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Jeff Archer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tutorials 2 | 3 | ## Reverse Engineering 4 | 5 | - asyncrat_config_parser: AsyncRAT Malware Config Parser in Python (with Video Tutorial) 6 | - hacking_weaponizing_solitaire: Reverse Engineering and Weaponizing XP Solitaire (with Video Tutorial) 7 | - rct_full_res: Roller Coaster Tycoon (1999) Full Resolution Game Patch (with Video Tutorial) 8 | - rct_horror_mod: Roller Coaster Tycoon (1999) Mod and Tutorial on Graphics Manipulation & Shellcode 9 | 10 | ## jeFF0Falltrades master0Fnone Classes 11 | 12 | The jeFF0Falltrades master0Fnone Class series is a collection of free, long-form online courses made to make learning topics - like reverse engineering - more accessible (and fun) to everyone. 13 | 14 | See the README in this directory for all available classes. 15 | 16 | ## Machine Learning 17 | 18 | - simple_transaction_classifier: Simple Bank/Credit Card Transaction Classification Tool 19 | 20 | ## How to Download Individual Project Directories 21 | 22 | Oftentimes, you may just want to download one, or only a few, of these Tutorial projects, and not the whole repository. 23 | 24 | To do so, you can take advantage of Git's `sparse-checkout` command, as such: 25 | 26 | ```bash 27 | git clone --no-checkout https://github.com/jeFF0Falltrades/Tutorials.git 28 | cd Tutorials 29 | git sparse-checkout init --cone 30 | git sparse-checkout set master0Fnone_classes/1_x86_Demystified 31 | git pull origin master 32 | ``` 33 | 34 | In this example, only the code files in `master0Fnone_classes/1_x86_Demystified` and its subdirectories will be downloaded onto your machine. 35 | 36 | If you want to download multiple directories, you must include them all in your `set` command: 37 | 38 | ```bash 39 | git clone --no-checkout https://github.com/jeFF0Falltrades/Tutorials.git 40 | cd Tutorials 41 | git sparse-checkout init --cone 42 | git sparse-checkout set master0Fnone_classes/1_x86_Demystified asyncrat_config_parser/ 43 | git pull origin master 44 | ``` 45 | 46 | If you omit `master0Fnone_classes/1_x86_Demystified` from the above after cloning it with `sparse-checkout`, for example, it will be removed and only `asyncrat_config_parser` will be present. 47 | 48 | -------------------------------------------------------------------------------- /asyncrat_config_parser/README.md: -------------------------------------------------------------------------------- 1 | # AsyncRAT Configuration Parser 2 | ## Important Note 3 | An updated and more robust version of this parser has been published at: 4 | 5 | * https://github.com/jeFF0Falltrades/rat_king_parser 6 | 7 | This repository and its accompanying video are still very valuable for those learning malware analysis and tool automation, but the Rat King parser is considered more stable for production use. 8 | 9 | ## YouTube Video 10 | * https://youtu.be/xV0x7kNZ_Yc 11 | 12 | ## YARA Rule for Hunting 13 | * https://github.com/jeFF0Falltrades/YARA-Signatures/blob/master/Broadbased/asyncrat.yar 14 | 15 | ## Requirements 16 | ``` 17 | pip install cryptography 18 | ``` 19 | 20 | or 21 | 22 | ``` 23 | pip install -r requirements.txt 24 | ``` 25 | 26 | ## Usage 27 | ``` 28 | usage: asyncrat_config_parser.py [-h] [-d] file_paths [file_paths ...] 29 | 30 | positional arguments: 31 | file_paths One or more AsyncRAT payload file paths (deobfuscated) 32 | 33 | optional arguments: 34 | -h, --help show this help message and exit 35 | -d, --debug Enable debug logging 36 | ``` 37 | 38 | ## Example Input/Output 39 | ``` 40 | $ python3 asyncrat_config_parser.py ReverseMe.exe | python -m json.tool 41 | { 42 | "aes_key": "40766aef6f9d6980c001babeef7020446eff2ef31cf910cab59d5429d7a89c37", 43 | "aes_salt": "bfeb1e56fbcd973bb219022430a57843003d5644d21e62b9d4f180e7e6c33941", 44 | "config": { 45 | "Anti": "false", 46 | "BDOS": "false", 47 | "Certificate": "MIIE8jCCAtqgAwIBAgIQAMe2UpmBbjqdMItW7xySBzANBgkqhkiG9w0BAQ0FADAaMRgwFgYDVQQDDA9Bc3luY1JBVCBTZXJ2ZXIwIBcNMjExMjA3MDE0NTQ1WhgPOTk5OTEyMzEyMzU5NTlaMBoxGDAWBgNVBAMMD0FzeW5jUkFUIFNlcnZlcjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAPHqNpoIpvegBcEOCxChs8Nw2/fS/hTdwqoaD0KkwGB52FRN/YncQZm3N1uqPRRM+nDslfwCdPlF7m895TnA7CB6NPUrGdi3Sbt5OE30a4gNrguHdC8MNVqoBA3mWZfT/nxmzXn7/KJfUH9UWPTJbC32B7ELwR71RALMPzIgE78AtpC3uU3f8Y+QTDt5xSR3MhfGFnIaMUuMRyZUMO9VV/7Ik4t2uUSsnYB8M6c6ytHkFokxNxdQJTWuJEW5ckGbWyWXkZlkGBcGz/6/cH1PdJBa/jK73xfwgrNoKKKvv+bWoBUPGg98jsXAfhIBOhkb9XVSp9t7bLNDuAh2uhbQejI3ZbSGwOYWWJGZYue92VuY/KPDZabD8IzwHLVdNAfQNSNzJXBycljYFzpErYp28j81/d2l+2I2DTdf1eZKiHHvNxeHPRpCNt05bk5NTl4gAtnOKwErV181jRnjMiB2oWWxAF2p2whU+85OeUywz/Ge7gmRgFW/pIqSReBA874YLmwRih51U0V1rr+oQlkk6qpvDW5tdpsuodlVL8R6lDx3pE88uPXDdfUdqyfjdF9zKyuk4K55LmBAwD/MgB+XtnZA74n0zG/vKyJQJa8yeJpHXOwt7hgtg5HJt+8cMdwh9L3J7ZWRqZchFvNeYSM9DW5c/uff33txCaLWrlHdeJOPAgMBAAGjMjAwMB0GA1UdDgQWBBQ2SPyox4Jk9VX7tmV493O/6f9oNDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBDQUAA4ICAQDspo2PPJ/sPl8+SZQ7NXokPB8D8cFlFsDDSi2q1TWJQ+qtMO+74U2I2h2FBBTPH6hB+F/2NW1pq49B9n/v41Wg5YKbifR9mUnueTL//r0c8dW+Fr7+Teji4ZDuOPypL9/IbM3eLflTpofVhHRWZCZ5gboX16gCMQUoOPMNWFmssPX8SJSwNO1TqnaceWU6+bmHIpNuryU/tvut3wXQEV0x7lszB0veBYy9dkhwIz+Y8VfqH9ID4i2fn5ATepialht0v4TALwA65/Tc/y9aXcxPr9JbhSP+hLkE13QzHFUEmGOgvd7XHUJKkD7pU3grbxO75osuCWRSv7L1PRX5EvYMGMfe8mFWJioI0VEFgZqvMH5jSfCDN+bqAkJVmG5Y9a9b5DmbZARTujI0XvDbmzRYPuwqntU0HZryxGhiOnDTPKsguvpmaJuzUPW+BN5y3aGuuwAvp+++yAMihVpc77rqcpocn/j6olN9jtL0GAGqYQtBVlAgMVyLX47TOM1GxcL7S9TCwDJ/xvUMc4Hhw63bcpYX8SjssCFzhXCB3Xr79KdKxE78M5YGG8Jj3bzlxLQAqV/1air5+pDLmQq6bUJ2UZnHgZxG5K9QjsQU0emJP0IyashQMBCG2uGuElXh2W4oy4SmIj5BE0ztFpDqQwTGJAzgrXIMBj8l4zJtKDlH2Q==", 48 | "Delay": "3", 49 | "Group": "Default", 50 | "Hosts": "test.me.com", 51 | "Install": "false", 52 | "InstallFile": "", 53 | "InstallFolder": "%AppData%", 54 | "Key": "N3UwelhLaE5BaTE5Z3piMFEwMFZlWHI2Z01Nc3dPOWM=", 55 | "MTX": "AsyncMutex_6SI8OkPnk", 56 | "Pastebin": "null", 57 | "Ports": "8808,7707", 58 | "Serversignature": "ZKSsdlzb5lEwgaF35KH+qv8Ai7M74R+W9CU2NpGy4ucvLuKhDbUpJtqllJuFAk22wP6qgCQ8lvE8zy+LlVHmCRovNGIcLKEIERkQGZDB7+puIsVLV2dzrcO4uRdKdcQWef+9T8rpzS3uuQ3YXuBwCEq70GpIz3ngiq9vlukj+ZvdBCVZRYve+Pc2dHsUPU413OpWFRQsOSnCZPohd0IzdrD4AXOBIeBOlpFFfuLzNaA5rze7l+6ovEG8G31A+GafpXEso5Hs3dD7y7hy1mL1Wf7pH0Mu94bUfYfQamCZxWrGHIDa4O+2uEkrEsWKkvDDVXzLq8HxaSJBYhixq3mesV79zWX+ZytTN5R7kdDBN5+ZyICQgjWtUTAyoIulylwbHcSVZ4ODv+5R29OD/7RoHFfsQNCHjGvMIWiVsTUqi6fLGj3GButFDZldse10P4gaeDn6h6bfsfNCGK5J+86slLI3/kR/PSV71tH4ABWv/pgWFbRnRTbDUmuCekAdG8c1yBObyYIphRNjNqRoH8Lu5o+onzAT7giI1EdMAGDase5cQ3T9DOC9XDTJ6OFYdbz9RFQSKQGM1UuSecIi/CDQ9XvyHMYUr0yjXhYzbXEFSdydwr0nSz//Nk+7nPS4NiY2MuA/uJKvG4YkFJkG13eUltweM8Zeb1FzZPaw0CRDmes=", 59 | "Version": "0.5.7B" 60 | }, 61 | "file_path": "ReverseMe.exe" 62 | } 63 | ``` 64 | -------------------------------------------------------------------------------- /asyncrat_config_parser/asyncrat_config_parser.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # asyncrat_config_parser.py 4 | # 5 | # Author: jeFF0Falltrades 6 | # 7 | # A configuration parser for the AsyncRAT malware family 8 | # written in Python (3.x). 9 | # 10 | # This module requires the pyca/cryptography library: 11 | # https://cryptography.io/en/latest/ 12 | # pip install cryptography 13 | # 14 | # Be aware that this parser was written as part of a tutorial to be used with 15 | # the associated video, and prioritizes explicitness and clarity over 16 | # performance. 17 | # 18 | # Feel free to slice it, dice it, and use it however it best works 19 | # for you IAW with the license below. 20 | # 21 | # Please submit Issues and Pull Requests for bugs to the project homepage below. 22 | # 23 | # Feel free to also reach out to me on Twitter @jeFF0Falltrades with any 24 | # feedback or issues. 25 | # 26 | # Homepage with Video Tutorial: 27 | # https://github.com/jeFF0Falltrades/Tutorials/tree/master/asyncrat_config_parser 28 | # 29 | # YARA rule to find samples: 30 | # https://github.com/jeFF0Falltrades/YARA-Signatures/blob/master/Broadbased/asyncrat.yar 31 | # 32 | # 33 | # MIT License 34 | # 35 | # Copyright (c) 2022 Jeff Archer 36 | # 37 | # Permission is hereby granted, free of charge, to any person obtaining a copy 38 | # of this software and associated documentation files (the "Software"), to deal 39 | # in the Software without restriction, including without limitation the rights 40 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 41 | # copies of the Software, and to permit persons to whom the Software is 42 | # furnished to do so, subject to the following conditions: 43 | # 44 | # The above copyright notice and this permission notice shall be included in all 45 | # copies or substantial portions of the Software. 46 | # 47 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 48 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 49 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 50 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 51 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 52 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 53 | # SOFTWARE. 54 | 55 | from argparse import ArgumentParser 56 | from base64 import b64decode 57 | from cryptography.hazmat.backends import default_backend 58 | from cryptography.hazmat.primitives.ciphers import Cipher 59 | from cryptography.hazmat.primitives.ciphers.algorithms import AES 60 | from cryptography.hazmat.primitives.ciphers.modes import CBC 61 | from cryptography.hazmat.primitives.padding import PKCS7 62 | from cryptography.hazmat.primitives.hashes import SHA1 63 | from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC 64 | from json import dumps 65 | from logging import basicConfig, DEBUG, exception, getLogger, WARNING 66 | from re import DOTALL, findall, search 67 | 68 | logger = getLogger(__name__) 69 | 70 | 71 | # An importable parser class which encompasses all operations for parsing and 72 | # decrypting (unobfuscated) AsyncRAT payload configurations 73 | class AsyncRATParser: 74 | 75 | OPCODE_RET = b'\x2a' 76 | PATTERN_CLR_METADATA_START = b'\x42\x53\x4a\x42' 77 | PATTERN_CONFIG_START = b'(\x72.{9}){9}' 78 | PATTERN_PARSED_RVAS = b'\x72(.{4})\x80(.{4})' 79 | RVA_STRINGS_BASE = 0x04000000 80 | RVA_US_BASE = 0x70000000 81 | STREAM_IDENTIFIER_STORAGE = b'#~' 82 | STREAM_IDENTIFIER_STRINGS = b'#Strings' 83 | STREAM_IDENTIFIER_US = b'#US' 84 | TABLE_FIELD = 'Field' 85 | 86 | # This map assists in calculating offsets for Field and FieldRVA entries. 87 | # 88 | # These calculations require knowing: 89 | # 1. The size of a row in the specified table (hardcoded here) 90 | # 2. The number of rows in the table (calculated in get_table_map()) 91 | # 92 | # We have only hardcoded in row sizes through the FieldRVA table because 93 | # this is the last table we need for our parser to function. 94 | # 95 | # We still include the remaining tables because we will need to check if 96 | # these are present to calculate the offset to a table in get_table_start(). 97 | # 98 | # If you are interested in generalizing this map to examine other tables, 99 | # I recommend checking out dnSpy's algorithm for calculating table row size 100 | # for the remaining tables here: 101 | # 102 | # https://github.com/dnSpy/dnSpy/blob/2b6dcfaf602fb8ca6462b8b6237fdfc0c74ad994/dnSpy/dnSpy/Hex/Files/DotNet/DotNetTableSizes.cs 103 | MAP_TABLE = { 104 | 'Module': { 105 | 'row_size': 10 106 | }, 107 | 'TypeRef': { 108 | 'row_size': 6 109 | }, 110 | 'TypeDef': { 111 | 'row_size': 14 112 | }, 113 | 'FieldPtr': { 114 | 'row_size': 2 115 | }, 116 | 'Field': { 117 | 'row_size': 6 118 | }, 119 | 'MethodPtr': { 120 | 'row_size': 2 121 | }, 122 | 'Method': { 123 | 'row_size': 14 124 | }, 125 | 'ParamPtr': { 126 | 'row_size': 2 127 | }, 128 | 'Param': { 129 | 'row_size': 6 130 | }, 131 | 'InterfaceImpl': { 132 | 'row_size': 4 133 | }, 134 | 'MemberRef': { 135 | 'row_size': 6 136 | }, 137 | 'Constant': { 138 | 'row_size': 6 139 | }, 140 | 'CustomAttribute': { 141 | 'row_size': 6 142 | }, 143 | 'FieldMarshal': { 144 | 'row_size': 4 145 | }, 146 | 'DeclSecurity': { 147 | 'row_size': 6 148 | }, 149 | 'ClassLayout': { 150 | 'row_size': 8 151 | }, 152 | 'FieldLayout': { 153 | 'row_size': 6 154 | }, 155 | 'StandAloneSig': { 156 | 'row_size': 2 157 | }, 158 | 'EventMap': { 159 | 'row_size': 4 160 | }, 161 | 'EventPtr': { 162 | 'row_size': 2 163 | }, 164 | 'Event': { 165 | 'row_size': 6 166 | }, 167 | 'PropertyMap': { 168 | 'row_size': 4 169 | }, 170 | 'PropertyPtr': { 171 | 'row_size': 2 172 | }, 173 | 'Property': { 174 | 'row_size': 6 175 | }, 176 | 'MethodSemantics': { 177 | 'row_size': 6 178 | }, 179 | 'MethodImpl': { 180 | 'row_size': 6 181 | }, 182 | 'ModuleRef': { 183 | 'row_size': 2 184 | }, 185 | 'TypeSpec': { 186 | 'row_size': 2 187 | }, 188 | 'ImplMap': { 189 | 'row_size': 8 190 | }, 191 | 'FieldRVA': { 192 | 'row_size': 6 193 | }, 194 | 'ENCLog': {}, 195 | 'ENCMap': {}, 196 | 'Assembly': {}, 197 | 'AssemblyProcessor': {}, 198 | 'AssemblyOS': {}, 199 | 'AssemblyRef': {}, 200 | 'AssemblyRefProcessor': {}, 201 | 'AssemblyRefOS': {}, 202 | 'File': {}, 203 | 'ExportedType': {}, 204 | 'ManifestResource': {}, 205 | 'NestedClass': {}, 206 | 'GenericParam': {}, 207 | 'MethodSpec': {}, 208 | 'GenericParamConstraint': {}, 209 | 'Reserved 2D': {}, 210 | 'Reserved 2E': {}, 211 | 'Reserved 2F': {}, 212 | 'Document': {}, 213 | 'MethodDebugInformation': {}, 214 | 'LocalScope': {}, 215 | 'LocalVariable': {}, 216 | 'LocalConstant': {}, 217 | 'ImportScope': {}, 218 | 'StateMachineMethod': {}, 219 | 'CustomDebugInformation': {}, 220 | 'Reserved 38': {}, 221 | 'Reserved 39': {}, 222 | 'Reserved 3A': {}, 223 | 'Reserved 3B': {}, 224 | 'Reserved 3C': {}, 225 | 'Reserved 3D': {}, 226 | 'Reserved 3E': {}, 227 | 'Reserved 3F': {} 228 | } 229 | 230 | # This nested class encapsulates all operations around parsing AES 231 | # decryption data from the file (e.g. key, salt, iterations, sizes) and 232 | # exposes a decrypt() method that our parent parser can use. 233 | # 234 | # TBF, it is not necessary to encapsulate these into an inner class like 235 | # this, but I think it helps organize the code a bit better. 236 | class ASyncRATAESDecryptor: 237 | OPCODE_LDSTR = b'\x72' 238 | OPCODE_LDTOKEN = b'\xd0' 239 | PATTERN_AES_KEY = b'\x7e(.{3}\x04)\x73' 240 | PATTERN_AES_KEY_AND_BLOCK_SIZE = b'\x07\x20(.{4})\x6f.{4}\x07\x20(.{4})' 241 | PATTERN_AES_METADATA = b'\x73.{4}\x7a\x03\x7e(.{4})' 242 | PATTERN_AES_SALT_INIT = b'\x80%b\x2a' 243 | SECTION_IDENTIFIER_TEXT = b'.text' 244 | TABLE_FIELD_RVA = 'FieldRVA' 245 | 246 | # We pass our parser in as parent_parser so that we can access all of 247 | # its attributes and methods from our inner class via self.parent 248 | def __init__(self, parent_parser): 249 | self.parent = parent_parser 250 | self.aes_metadata_flag = self.get_aes_metadata_flag() 251 | self.key_size, self.block_size = self.get_aes_key_and_block_size() 252 | self.iterations = self.get_aes_iterations() 253 | self.salt = self.get_aes_salt() 254 | # Call this last as we need the above attributes to derive the key 255 | self.key = self.get_aes_key() 256 | 257 | # Given an initialization vector and ciphertext, creates a Cipher 258 | # object with the AES key and specified IV and decrypts the ciphertext 259 | def decrypt(self, iv, ciphertext): 260 | logger.debug( 261 | f'Decrypting {ciphertext} with key {self.key.hex()} and IV {iv.hex()}...' 262 | ) 263 | aes_cipher = Cipher(AES(self.key), 264 | CBC(iv), 265 | backend=default_backend()) 266 | decryptor = aes_cipher.decryptor() 267 | # Use a PKCS7 unpadder to remove padding from decrypted value 268 | # https://cryptography.io/en/latest/hazmat/primitives/padding/ 269 | unpadder = PKCS7(self.block_size).unpadder() 270 | try: 271 | padded_text = decryptor.update( 272 | ciphertext) + decryptor.finalize() 273 | unpadded_text = unpadder.update( 274 | padded_text) + unpadder.finalize() 275 | except Exception as e: 276 | raise self.parent.ASyncRATParserError( 277 | f'Error decrypting ciphertext {ciphertext} with IV {iv.hex()} and key {self.key.hex()}' 278 | ) from e 279 | logger.debug(f'Decryption result: {unpadded_text}') 280 | return unpadded_text 281 | 282 | # Given a field ID from the Field table, returns the relative virtual 283 | # address of the field, e.g.: 284 | # 285 | # Field RVA: 0x0400001D 286 | # Field ID = 0x1D 287 | # FieldRVA Entry for Field ID: 0x1D : 0x2050 288 | # Final RVA: 0x2050 289 | def field_id_to_field_rva(self, id): 290 | fieldrva_table_start = self.parent.get_table_start( 291 | self.TABLE_FIELD_RVA) 292 | field_rva = None 293 | matched = False 294 | # Start at the beginning of the FieldRVA table 295 | cur_offset = fieldrva_table_start 296 | for x in range( 297 | self.parent.table_map[self.TABLE_FIELD_RVA]['num_rows']): 298 | try: 299 | field_id = self.parent.bytes_to_int( 300 | self.parent.data[cur_offset + 4:cur_offset + 6]) 301 | field_rva = self.parent.bytes_to_int( 302 | self.parent.data[cur_offset:cur_offset + 4]) 303 | # Break if our matching ID is found in this row 304 | if field_id == id: 305 | matched = True 306 | break 307 | # Otherwise, keep moving through the table 308 | cur_offset += self.parent.table_map[ 309 | self.TABLE_FIELD_RVA]['row_size'] 310 | except Exception as e: 311 | raise self.parent.ASyncRATParserError( 312 | f'Error parsing FieldRVA corresponding to ID {id}' 313 | ) from e 314 | # If we never found our match, raise an exception 315 | if not matched: 316 | raise self.parent.ASyncRATParserError( 317 | f'Could not find FieldRVA corresponding to ID {id}') 318 | return field_rva 319 | 320 | # Given an RVA from the FieldRVA table, calculates the file offset of 321 | # the field value by subtracting the relative virtual address of the 322 | # .text section and adding the file offset of the .text section, e.g. 323 | # 324 | # Field RVA: 0x2050 325 | # Text section RVA: 0x2000 326 | # Text section file offset: 0x0200 327 | # Field offset = 0x2050 - 0x2000 + 0x0200 328 | # = 0x0250 329 | def field_rva_to_offset(self, field_rva): 330 | text_section_metadata_offset = self.parent.data.find( 331 | self.SECTION_IDENTIFIER_TEXT) 332 | if text_section_metadata_offset == -1: 333 | raise self.parent.ASyncRATParserError( 334 | 'Could not identify .text section metadata') 335 | text_section_rva = self.parent.bytes_to_int( 336 | self.parent.data[text_section_metadata_offset + 337 | 12:text_section_metadata_offset + 16]) 338 | text_section_offset = self.parent.bytes_to_int( 339 | self.parent.data[text_section_metadata_offset + 340 | 20:text_section_metadata_offset + 24]) 341 | field_offset = field_rva - text_section_rva + text_section_offset 342 | return field_offset 343 | 344 | # Extracts the AES iteration number from the payload 345 | def get_aes_iterations(self): 346 | logger.debug('Extracting AES iterations...') 347 | iterations_offset_start = self.aes_metadata_flag.end() + 1 348 | iterations_val_packed = self.parent.data[ 349 | iterations_offset_start:iterations_offset_start + 2] 350 | iterations = self.parent.bytes_to_int(iterations_val_packed) 351 | logger.debug(f'Found AES iteration number of {iterations}') 352 | return iterations 353 | 354 | # Identifies the initialization of the AES256 object in the payload by 355 | # looking for the following ops: 356 | # 357 | # newobj instance void [mscorlib]System.ArgumentException... 358 | # throw 359 | # ldarg.1 360 | # ldsfld uint8[] Client.Algorithm.Aes256::Salt 361 | def get_aes_metadata_flag(self): 362 | logger.debug('Extracting AES metadata flag...') 363 | # Important to use DOTALL here (and with all regex ops to be safe) 364 | # as we are working with bytes, and if we do not set this, and the 365 | # byte sequence contains a byte that equates to a newline 366 | # (\n or 0x0A), the search will fail 367 | md_flag_offset = search(self.PATTERN_AES_METADATA, 368 | self.parent.data, DOTALL) 369 | if md_flag_offset is None: 370 | raise self.parent.ASyncRATParserError( 371 | 'Could not identify AES metadata flag') 372 | logger.debug( 373 | f'AES metadata flag found at offset {hex(md_flag_offset.start())}' 374 | ) 375 | return md_flag_offset 376 | 377 | # Extracts the AES key from the payload using a regex pattern which 378 | # looks for the initialization of the key - specifically, the following 379 | # ops: 380 | # 381 | # ldsfld string Client.Settings::Key 382 | # newobj instance void Client.Algorithm.Aes256::.ctor(string) 383 | def get_aes_key(self): 384 | logger.debug('Extracting encoded AES key value...') 385 | hit = search(self.PATTERN_AES_KEY, self.parent.data, DOTALL) 386 | if hit is None: 387 | raise self.parent.ASyncRATParserError( 388 | 'Could not find AES key pattern') 389 | 390 | # Since we already have a map of all fields, and have translated 391 | # config values (including Key) into translated_config, to find the 392 | # key value, we take the RVA of the key, look up its value in our 393 | # fields_map, and then look up that same value in our translated 394 | # _config 395 | # 396 | # Key RVA: 0x04000007 397 | # Key Field Map Index = 0x04000007 - 0x04000000 - 1 = 6 398 | # Key Field Name Value = fields_map[6] 399 | # Key Value = translated_config[fields_map[6]] 400 | key_field_rva = self.parent.bytes_to_int(hit.groups()[0]) 401 | key_field_name = self.parent.strings_rva_to_strings_val( 402 | key_field_rva) 403 | key_val = self.parent.translated_config[key_field_name] 404 | logger.debug(f'AES encoded key value found: {key_val}') 405 | try: 406 | passphrase = b64decode(key_val) 407 | except Exception as e: 408 | raise self.parent.ASyncRATParserError( 409 | f'Error decoding key value {key_val}') from e 410 | logger.debug(f'AES passphrase found: {passphrase}') 411 | # The backend parameter is optional in newer versions of the 412 | # cryptography library, but we keep it here for compatibility 413 | kdf = PBKDF2HMAC(SHA1(), 414 | length=self.key_size, 415 | salt=self.salt, 416 | iterations=self.iterations, 417 | backend=default_backend()) 418 | try: 419 | key = kdf.derive(passphrase) 420 | except Exception as e: 421 | raise self.parent.ASyncRATParserError( 422 | f'Error deriving key from passphrase {passphrase}') from e 423 | logger.debug(f'AES key derived: {key.hex()}') 424 | return key 425 | 426 | # Extracts the AES key and block size from the payload 427 | def get_aes_key_and_block_size(self): 428 | logger.debug('Extracting AES key and block size...') 429 | hit = search(self.PATTERN_AES_KEY_AND_BLOCK_SIZE, self.parent.data, 430 | DOTALL) 431 | if hit is None: 432 | raise self.parent.ASyncRATParserError( 433 | 'Could not extract AES key or block size') 434 | # Convert key size from bits to bytes by dividing by 8 435 | # Note use of // instead of / to ensure integer output, not float 436 | key_size = self.parent.bytes_to_int(hit.groups()[0]) // 8 437 | block_size = self.parent.bytes_to_int(hit.groups()[1]) 438 | logger.debug( 439 | f'Found key size {key_size} and block size {block_size}') 440 | return key_size, block_size 441 | 442 | # Extracts the AES salt from the payload, accounting for both hardcoded 443 | # salt byte arrays, and salts derived from hardcoded strings 444 | def get_aes_salt(self): 445 | logger.debug('Extracting AES salt value...') 446 | # The Salt RVA was captured in our metadata flag pattern 447 | aes_salt_rva = self.aes_metadata_flag.groups()[0] 448 | # Use % to insert our salt RVA into our match pattern 449 | # This pattern will then find the salt initialization ops, 450 | # specifically: 451 | # 452 | # stsfld uint8[] Client.Algorithm.Aes256::Salt 453 | # ret 454 | aes_salt_initialization = self.parent.data.find( 455 | self.PATTERN_AES_SALT_INIT % aes_salt_rva) 456 | if aes_salt_initialization == -1: 457 | raise self.parent.ASyncRATParserError( 458 | 'Could not identify AES salt initialization') 459 | 460 | # Look at opcode used to initialize the salt to decide how to 461 | # proceed on extracting the salt value (start of pattern - 10 bytes) 462 | salt_op_offset = aes_salt_initialization - 10 463 | # Need to use bytes([int]) here to properly convert from int to byte 464 | # string for our comparison below 465 | salt_op = bytes([self.parent.data[salt_op_offset]]) 466 | 467 | # Get the salt RVA from the 4 bytes following the initialization op 468 | salt_strings_rva_packed = self.parent.data[salt_op_offset + 469 | 1:salt_op_offset + 5] 470 | salt_strings_rva = self.parent.bytes_to_int( 471 | salt_strings_rva_packed) 472 | 473 | # If the op is a ldstr op, just get the bytes value of the string 474 | # being used to initialize the salt 475 | if salt_op == self.OPCODE_LDSTR: 476 | salt_encoded = self.parent.us_rva_to_us_val(salt_strings_rva) 477 | # We use decode_bytes() here to get the salt string without any 478 | # null bytes (because it's stored as UTF-16LE), then convert it 479 | # back to bytes 480 | salt = self.parent.decode_bytes(salt_encoded).encode() 481 | # If the op is a ldtoken operation, we need to get the salt byte 482 | # array value from the FieldRVA table 483 | elif salt_op == self.OPCODE_LDTOKEN: 484 | salt = self.get_aes_salt_ldtoken_method( 485 | salt_strings_rva, salt_op_offset) 486 | else: 487 | raise self.parent.ASyncRATParserError( 488 | f'Unknown salt opcode found: {salt_op.hex()}') 489 | logger.debug(f'Found salt value: {salt.hex()}') 490 | return salt 491 | 492 | # Derives the AES salt by loading the RVA of the salt from 493 | # the FieldRVA table, converting it to a file offset, and 494 | # reading the salt value from that offset 495 | def get_aes_salt_ldtoken_method(self, salt_strings_rva, 496 | salt_op_offset): 497 | salt_size = self.parent.data[salt_op_offset - 7] 498 | # Salt field ID = Salt strings RVA - #Strings RVA base 499 | salt_field_id = salt_strings_rva - self.parent.RVA_STRINGS_BASE 500 | salt_field_rva = self.field_id_to_field_rva(salt_field_id) 501 | salt_offset = self.field_rva_to_offset(salt_field_rva) 502 | salt_value = self.parent.data[salt_offset:salt_offset + salt_size] 503 | return salt_value 504 | 505 | # Custom exception class to provide detailed exceptions from the parser 506 | class ASyncRATParserError(Exception): 507 | pass 508 | 509 | def __init__(self, file_path): 510 | self.file_path = file_path 511 | self.data = self.get_file_data() 512 | self.table_map = self.get_table_map() 513 | self.fields_map = self.get_fields_map() 514 | self.config_addr_map = self.get_config_address_map() 515 | self.translated_config = self.get_translated_config() 516 | self.aes_decryptor = self.ASyncRATAESDecryptor(self) 517 | self.config = self.decrypt_config() 518 | 519 | # Converts a bytes object to an int object using the specified byte order 520 | # (little-endian by default) 521 | def bytes_to_int(self, bytes, order='little'): 522 | try: 523 | result = int.from_bytes(bytes, byteorder=order) 524 | except Exception as e: 525 | raise self.ASyncRATParserError( 526 | f'Error parsing int from value: {bytes}') from e 527 | return result 528 | 529 | # Decodes a bytes object to a Unicode string, using UTF-16LE for byte values 530 | # with null bytes still embedded in them, and UTF-8 for all other values 531 | def decode_bytes(self, byte_str): 532 | result = None 533 | try: 534 | if b'\x00' in byte_str: 535 | result = byte_str.decode('utf-16le') 536 | else: 537 | result = byte_str.decode('utf-8') 538 | except Exception as e: 539 | raise self.ASyncRATParserError( 540 | f'Error decoding bytes object to Unicode: {byte_str}') from e 541 | return result 542 | 543 | # Given a translated config containing config field names and encrypted 544 | # and/or base64-encoded field values, decodes and decrypts encrypted values 545 | # and returns the decrypted config 546 | def decrypt_config(self): 547 | logger.debug('Decrypting config...') 548 | decrypted_config = {} 549 | for k, v in self.translated_config.items(): 550 | # We convert config field names and values from bytes to Unicode 551 | # strings for compatibility with JSON output 552 | decoded_k = self.decode_bytes(k) 553 | b64_exception = False 554 | decrypted_config[decoded_k] = self.decode_bytes(v) 555 | 556 | # Leave empty strings as they are 557 | if len(v) == 0: 558 | logger.debug( 559 | f'Key: {decoded_k}, Value: {decrypted_config[decoded_k]}') 560 | continue 561 | # Check if base64-encoded string 562 | try: 563 | decoded_val = b64decode(v) 564 | except: 565 | b64_exception = True 566 | # If it was not base64-encoded, or if it is less than our min length 567 | # for ciphertext, leave the value as it is 568 | if b64_exception or len(decoded_val) < 48: 569 | logger.debug( 570 | f'Key: {decoded_k}, Value: {decrypted_config[decoded_k]}') 571 | continue 572 | # Otherwise, extract the IV from the 16 bytes after the HMAC 573 | # (first 32 bytes) and the ciphertext from the rest of the data 574 | # after the IV, and run the decryption 575 | iv, ciphertext = decoded_val[32:48], decoded_val[48:] 576 | decrypted_config[decoded_k] = self.decode_bytes( 577 | self.aes_decryptor.decrypt(iv, ciphertext)) 578 | logger.debug( 579 | f'Key: {decoded_k}, Value: {decrypted_config[decoded_k]}') 580 | logger.debug('Successfully decrypted config') 581 | return decrypted_config 582 | 583 | # Searches for the AsyncRAT configuration section in the Settings module, 584 | # and attempts to extract the RVAs of the config field names and values 585 | # 586 | # Specifically, looks for ldstr used >= 9 times in a row 587 | def get_config_address_map(self): 588 | logger.debug('Extracting the config address map...') 589 | config_mappings = [] 590 | hit = search(self.PATTERN_CONFIG_START, self.data, DOTALL) 591 | if hit is None: 592 | raise self.ASyncRATParserError('Could not find start of config') 593 | config_start = hit.start() 594 | # Configuration ends with ret operation, so we get string using the ret 595 | # opcode (0x2A) as our terminating char 596 | parsed_ops = self.get_string_from_offset(config_start, self.OPCODE_RET) 597 | # Split the field name RVAs from the field value RVAs in our parsed ops 598 | parsed_rvas = findall(self.PATTERN_PARSED_RVAS, parsed_ops, DOTALL) 599 | for (us_rva, string_rva) in parsed_rvas: 600 | config_value_rva = self.bytes_to_int(us_rva) 601 | config_name_rva = self.bytes_to_int(string_rva) 602 | logger.debug( 603 | f'Found config item: ({hex(config_value_rva)}, {hex(config_name_rva)})' 604 | ) 605 | config_mappings.append((config_value_rva, config_name_rva)) 606 | logger.debug('Successfully extracted config address map') 607 | return config_mappings 608 | 609 | # Extracts the Field table of the assembly, mapping the value of each 610 | # field from the #Strings stream to its offset in the Field table 611 | def get_fields_map(self): 612 | logger.debug('Extracting fields map...') 613 | fields_map = [] 614 | fields_start = self.get_table_start(self.TABLE_FIELD) 615 | strings_start = self.get_stream_start(self.STREAM_IDENTIFIER_STRINGS) 616 | # Start at the beginning of the Field table 617 | cur_offset = fields_start 618 | for x in range(self.table_map[self.TABLE_FIELD]['num_rows']): 619 | try: 620 | field_offset = self.bytes_to_int(self.data[cur_offset + 621 | 2:cur_offset + 4]) 622 | field_value = self.get_string_from_offset(strings_start + 623 | field_offset) 624 | # Proceed to next row 625 | cur_offset += self.table_map[self.TABLE_FIELD]['row_size'] 626 | except Exception as e: 627 | raise self.ASyncRATParserError( 628 | 'Error parsing Field table') from e 629 | logger.debug(f'Found field: {hex(field_offset)}, {field_value}') 630 | fields_map.append((field_value, field_offset)) 631 | logger.debug('Successfully extracted fields map') 632 | return fields_map 633 | 634 | # Given a file path, reads in and returns binary contents from that path 635 | def get_file_data(self): 636 | logger.debug(f'Reading contents from: {self.file_path}') 637 | try: 638 | with open(self.file_path, 'rb') as fp: 639 | data = fp.read() 640 | except Exception as e: 641 | raise self.ASyncRATParserError( 642 | f'Error reading from path: {self.file_path}') from e 643 | logger.debug('Successfully read data') 644 | return data 645 | 646 | # Extracts the m_maskvalid value from the Tables Stream 647 | def get_mask_valid(self): 648 | logger.debug('Extracting m_maskvalid value...') 649 | storage_stream_offset = self.get_stream_start( 650 | self.STREAM_IDENTIFIER_STORAGE) 651 | mask_valid_offset = storage_stream_offset + 8 652 | mask_valid = self.bytes_to_int( 653 | self.data[mask_valid_offset:mask_valid_offset + 8]) 654 | logger.debug(f'Extracted m_maskvalid: {hex(mask_valid)}') 655 | return mask_valid 656 | 657 | # Finds the start of the Common Language Runtime (CLR) metadata header 658 | # using the metadata start flag (0x424a5342) 659 | def get_metadata_header_offset(self): 660 | hit = self.data.find(self.PATTERN_CLR_METADATA_START) 661 | if hit == -1: 662 | raise self.ASyncRATParserError( 663 | 'Could not find start of CLR metadata header') 664 | return hit 665 | 666 | # Given a stream identifier (e.g. #Strings or #US), finds the file offset 667 | # of the start of the stream 668 | def get_stream_start(self, stream_identifier): 669 | metadata_header_offset = self.get_metadata_header_offset() 670 | hit = self.data.find(stream_identifier) 671 | if hit == -1: 672 | raise self.ASyncRATParserError( 673 | f'Could not find offset of stream {stream_identifier}') 674 | stream_offset = self.bytes_to_int(self.data[hit - 8:hit - 4]) 675 | return metadata_header_offset + stream_offset 676 | 677 | # Given a string offset and, optionally, a delimiter (default terminating 678 | # null byte), extracts a string from the offset 679 | def get_string_from_offset(self, str_offset, delimiter=b'\0'): 680 | try: 681 | result = self.data[str_offset:].partition(delimiter)[0] 682 | except Exception as e: 683 | raise self.ASyncRATParserError( 684 | f'Could not extract string value from offset {hex(str_offset)} with delimiter {delimiter}' 685 | ) from e 686 | return result 687 | 688 | # Creates a copy of the table map template above and populates it with 689 | # extracted table data from the payload, including tables present and 690 | # number of rows per table 691 | def get_table_map(self): 692 | logger.debug('Extracting table map...') 693 | mask_valid = self.get_mask_valid() 694 | # Ensure we use copy() here because, if not, every newly instantiated 695 | # AsyncRATParser object will have the same table map, and changes to 696 | # one will impact all others 697 | table_map = self.MAP_TABLE.copy() 698 | storage_stream_offset = self.get_stream_start( 699 | self.STREAM_IDENTIFIER_STORAGE) 700 | # Table row counts start 24 bytes after storage stream start 701 | table_start = storage_stream_offset + 24 702 | cur_offset = table_start 703 | try: 704 | for table in table_map: 705 | # m_maskvalid tells us which tables are present in this 706 | # particular assembly, with each table receiving one bit in the 707 | # mask. If this bit is set to 1, the table is present. 708 | # 709 | # So to check for table presence, we use the bitwise formula: 710 | # 711 | # Table Present = m_maskvalid AND (2^table index) 712 | if mask_valid & (2**list(table_map.keys()).index(table)): 713 | row_count_packed = self.data[cur_offset:cur_offset + 4] 714 | row_count = self.bytes_to_int(row_count_packed) 715 | table_map[table]['num_rows'] = row_count 716 | logger.debug(f'Found {row_count} rows for table {table}') 717 | cur_offset += 4 718 | else: 719 | table_map[table]['num_rows'] = 0 720 | except Exception as e: 721 | raise self.ASyncRATParserError( 722 | 'Could not get counts of rows from tables') from e 723 | logger.debug('Successfully extracted table map') 724 | return table_map 725 | 726 | # Given the name of a table in the Tables Stream, finds the offset pointing 727 | # to the start of that table 728 | def get_table_start(self, table_name): 729 | storage_stream_offset = self.get_stream_start( 730 | self.STREAM_IDENTIFIER_STORAGE) 731 | # Table Start = Table Stream Start + 4 bytes for the row size of each 732 | # table present in the assembly 733 | # = (24 + Storage stream start) + (4 * each table present 734 | # in the assembly) 735 | tables_start_offset = storage_stream_offset + 24 + (4 * len([ 736 | table for table in self.table_map 737 | if self.table_map[table]['num_rows'] > 0 738 | ])) 739 | 740 | table_offset = tables_start_offset 741 | for table in self.table_map: 742 | # Break if we have found our table 743 | if table == table_name: 744 | break 745 | # If we no longer find 'row_size', we are past the point we should 746 | # be, at least for our parser, as that means we've passed the 747 | # FieldRVA table 748 | elif 'row_size' not in self.table_map[table]: 749 | raise self.ASyncRATParserError('Invalid table offset found') 750 | # Increment by (row size * number of rows in current table) bytes 751 | table_offset += self.table_map[table]['row_size'] * self.table_map[ 752 | table]['num_rows'] 753 | return table_offset 754 | 755 | # Translates encrypted config addresses to their values from their RVAs 756 | def get_translated_config(self): 757 | logger.debug('Translating configuration addresses to values...') 758 | translated_config = {} 759 | for (us_rva, strings_rva) in self.config_addr_map: 760 | try: 761 | field_name = self.strings_rva_to_strings_val(strings_rva) 762 | field_value = self.us_rva_to_us_val(us_rva) 763 | logger.debug( 764 | f'Found config value: {field_name} = {field_value}') 765 | translated_config[field_name] = field_value 766 | except Exception as e: 767 | raise self.ASyncRATParserError( 768 | f'Error translating RVAs {hex(us_rva)} and {hex(strings_rva)}' 769 | ) from e 770 | logger.debug('Successfully translated configuration') 771 | return translated_config 772 | 773 | # Returns metadata and the decrypted configuration for a payload 774 | def report(self): 775 | result_dict = { 776 | 'file_path': self.file_path, 777 | 'aes_key': self.aes_decryptor.key.hex(), 778 | 'aes_salt': self.aes_decryptor.salt.hex(), 779 | 'config': self.config 780 | } 781 | return result_dict 782 | 783 | # Given an RVA from the #Strings stream, extracts the value of the string 784 | # at that RVA using our extracted Fields map 785 | def strings_rva_to_strings_val(self, strings_rva): 786 | # Index of value in fields_map = RVA - #Strings base RVA - 1, e.g.: 787 | # 788 | # RVA: 0x04000001 789 | # Index = 0x04000001 - 0x04000000 - 1 = 0 790 | val_index = strings_rva - self.RVA_STRINGS_BASE - 1 791 | try: 792 | strings_val = self.fields_map[val_index][0] 793 | except Exception as e: 794 | raise self.ASyncRATParserError( 795 | f'Could not retrieve string from RVA {strings_rva}') from e 796 | return strings_val 797 | 798 | # Given an RVA from the #US stream, extracts the value of the string 799 | # at that RVA 800 | def us_rva_to_us_val(self, us_rva): 801 | us_start = self.get_stream_start(self.STREAM_IDENTIFIER_US) 802 | # Strings in the #US stream are prefaced with 1-2 length bytes 803 | # 804 | # If the length of the string is >= 128 bytes, 2 bytes will be used 805 | # to indicate length, and the first byte's most significant bit will be 806 | # set. 807 | # 808 | # So we first check if the first length byte & 0x80 (1000 0000) is 1. 809 | # 810 | # If so, then this indicates we are dealing with a long string, and we 811 | # grab the size from 2 length bytes instead of 1. 812 | # 813 | # If not, then we can assume there is only 1 length byte to read from 814 | # and then skip over it to get to the string 815 | length_byte_offset = us_rva - self.RVA_US_BASE + us_start 816 | if int(self.data[length_byte_offset]) & 0x80: 817 | val_offset = 2 818 | # Notice we use big-endianness here, and also that we subtract the 819 | # most significant bit of the two length bytes (0x8000 or 1000 0000 820 | # 0000 0000) serving as a flag 821 | val_size = self.bytes_to_int( 822 | self.data[length_byte_offset:length_byte_offset + 2], 823 | 'big') - 0x8000 824 | else: 825 | val_offset = 1 826 | val_size = self.data[length_byte_offset] 827 | val_offset += length_byte_offset 828 | # Subtract 1 to account for null terminator at the end of the string 829 | us_val = self.data[val_offset:val_offset + val_size - 1] 830 | return us_val 831 | 832 | 833 | if __name__ == '__main__': 834 | ap = ArgumentParser() 835 | ap.add_argument( 836 | 'file_paths', 837 | nargs='+', 838 | help='One or more AsyncRAT payload file paths (deobfuscated)') 839 | ap.add_argument('-d', 840 | '--debug', 841 | action='store_true', 842 | help='Enable debug logging') 843 | args = ap.parse_args() 844 | if args.debug: 845 | basicConfig(level=DEBUG) 846 | else: 847 | basicConfig(level=WARNING) 848 | 849 | decrypted_configs = [] 850 | for fp in args.file_paths: 851 | try: 852 | decrypted_configs.append(AsyncRATParser(fp).report()) 853 | except: 854 | logger.exception(f'Exception occurred for {fp}', exc_info=True) 855 | continue 856 | 857 | if len(decrypted_configs) > 0: 858 | print(dumps(decrypted_configs)) 859 | -------------------------------------------------------------------------------- /asyncrat_config_parser/asyncrat_derive_and_decrypt_cyberchef.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "op": "Register", 4 | "args": [ 5 | "(.+?)\\n(.+)", 6 | true, 7 | true, 8 | true 9 | ] 10 | }, 11 | { 12 | "op": "Derive PBKDF2 key", 13 | "args": [ 14 | { 15 | "option": "Base64", 16 | "string": "$R0" 17 | }, 18 | 256, 19 | 50000, 20 | "SHA1", 21 | { 22 | "option": "Hex", 23 | "string": "bfeb1e56fbcd973bb219022430a57843003d5644d21e62b9d4f180e7e6c33941" 24 | } 25 | ] 26 | }, 27 | { 28 | "op": "Register", 29 | "args": [ 30 | "([\\s\\S]*)", 31 | true, 32 | false, 33 | false 34 | ] 35 | }, 36 | { 37 | "op": "Find / Replace", 38 | "args": [ 39 | { 40 | "option": "Regex", 41 | "string": ".+" 42 | }, 43 | "$R1", 44 | false, 45 | false, 46 | false, 47 | false 48 | ] 49 | }, 50 | { 51 | "op": "Fork", 52 | "args": [ 53 | "\\n", 54 | "\\n", 55 | false 56 | ] 57 | }, 58 | { 59 | "op": "From Base64", 60 | "args": [ 61 | "A-Za-z0-9+/=", 62 | true 63 | ] 64 | }, 65 | { 66 | "op": "To Hex", 67 | "args": [ 68 | "None", 69 | 0 70 | ] 71 | }, 72 | { 73 | "op": "Register", 74 | "args": [ 75 | ".{64}(.{32})(.+)", 76 | true, 77 | false, 78 | false 79 | ] 80 | }, 81 | { 82 | "op": "Find / Replace", 83 | "args": [ 84 | { 85 | "option": "Regex", 86 | "string": ".+" 87 | }, 88 | "$R4", 89 | false, 90 | false, 91 | false, 92 | false 93 | ] 94 | }, 95 | { 96 | "op": "AES Decrypt", 97 | "args": [ 98 | { 99 | "option": "Hex", 100 | "string": "$R2" 101 | }, 102 | { 103 | "option": "Hex", 104 | "string": "$R3" 105 | }, 106 | "CBC", 107 | "Hex", 108 | "Raw", 109 | { 110 | "option": "Hex", 111 | "string": "" 112 | }, 113 | { 114 | "option": "Hex", 115 | "string": "" 116 | } 117 | ] 118 | } 119 | ] -------------------------------------------------------------------------------- /asyncrat_config_parser/requirements.txt: -------------------------------------------------------------------------------- 1 | cryptography -------------------------------------------------------------------------------- /disassembly_in_the_d4rk/1_splinter_cell/README.md: -------------------------------------------------------------------------------- 1 | # Reverse Engineering/Bashing Splinter Cell to see What Falls Out 2 | 3 | ## Video Link 4 | 5 | * https://youtu.be/1TeDlUv70ew 6 | 7 | ## Resources 8 | * [Blog post inspiring the initial investigation](https://www.martynassateika.lt/posts/2019/08/splinter-cell-thermal-vision-in-early-levels/) 9 | * [UE Viewer](https://www.gildor.org/en/projects/umodel) 10 | * [Exporting FBX from Blender to Unreal](https://docs.readyplayer.me/ready-player-me/integration-guides/unreal-engine/animations/blender-to-unreal-export) 11 | * [Gildor Page on Splinter Cell Files](https://www.gildor.org/smf/index.php/topic,8724.0.html) 12 | * [DecUbiSnd Download Page](https://bitbucket.org/Zenchreal/decubisnd/downloads/) 13 | 14 | ## Things We Did in This Episode 15 | * Evaluating boolean flags in game map files 16 | * Extracting game assets 17 | * Modifying game meshes, textures, and animations with Blender and Unreal Engine 18 | * Modifying data stick content 19 | * Exporting game audio 20 | -------------------------------------------------------------------------------- /disassembly_in_the_d4rk/README.md: -------------------------------------------------------------------------------- 1 | # Disassembly in the D4rk (DitD) Series 2 | 3 | DitD is a YouTube series of unstructured reverse engineering, software, game modding/hacking, and other content - as opposed to the structured Tutorials see elsewhere in this repo. 4 | 5 | This directory contains resources from each episode, including any code/scripts, files, or further reading which were mentioned in the video. 6 | 7 | ## Episode List 8 | 9 | 1. Reverse Engineering/Bashing Splinter Cell to see What Falls Out (*Tom Clancy's Splinter Cell*) 10 | -------------------------------------------------------------------------------- /hacking_weaponizing_solitaire/README.md: -------------------------------------------------------------------------------- 1 | # Reverse Engineering and Weaponizing XP Solitaire (Mini-Course) 2 | 3 | ## YouTube Video 4 | 5 | - https://youtu.be/ZmPArvsSii4 6 | 7 | ## Contents 8 | 9 | ``` 10 | ├───cards_dll_proxy --> CPP and DEF file to create weaponized cards.dll 11 | │ cards.def 12 | │ cards_proxy.cpp 13 | │ 14 | ├───card_generator --> Python script to generate custom card bitmaps 15 | │ card_generator.py 16 | │ requirements.txt 17 | │ 18 | └───example_files --> Example files from the video 19 | 0_C.bmp 20 | A_H.bmp 21 | A_S.bmp 22 | cards.rc 23 | cards.res 24 | cards_weaponized.dll 25 | D_C.bmp 26 | E_D.bmp 27 | F_D.bmp 28 | F_H.bmp 29 | F_S.bmp 30 | J_C.bmp 31 | L_C.bmp 32 | L_S.bmp 33 | R_H.bmp 34 | S_H.bmp 35 | T_D.bmp 36 | ``` 37 | 38 | ## Usage 39 | 40 | ### Generating Card Bitmaps/RES File 41 | 42 | #### Using card_generator.py 43 | 44 | ```bash 45 | $ python card_generator.py -h 46 | Usage: card_generator.py 47 | Edit script to select text, characters, and suits 48 | ``` 49 | 50 | #### Building the RES File 51 | 52 | _Note: Requires windres or rc.exe_ 53 | 54 | ```bash 55 | windres cards.rc -o cards.res 56 | ``` 57 | 58 | or 59 | 60 | ```bash 61 | rc.exe /fo cards.res cards.rc 62 | ``` 63 | 64 | ### Building the Weaponized DLL 65 | 66 | _Note: This is just one method; Visual Studio or another C++ compiler/IDE can be used_ 67 | 68 | 1. [Download MSYS2](https://www.msys2.org/) 69 | 2. Run MSYS2 and download GCC and 32/64-bit toolchains: 70 | 71 | ``` 72 | pacman -S mingw-w64-x86_64-gcc 73 | pacman -S --needed base-devel mingw-w64-x86_64-toolchain 74 | pacman -S --needed base-devel mingw-w64-i686-toolchain 75 | ``` 76 | 77 | 3. Compile the weaponized DLL: 78 | 79 | ```bash 80 | g++ -Werror -static -mdll -o cards_weaponized.dll cards_proxy.cpp cards.def 81 | ``` 82 | 83 | ## Remember: Use your knowledge and skills for good and fun, not evil (not even evil fun) 84 | 85 | Enjoy and please leave questions and feedback on [YouTube](https://www.youtube.com/@jeff0falltrades) or [Mastodon](https://infosec.exchange/@jeFF0Falltrades)! 86 | -------------------------------------------------------------------------------- /hacking_weaponizing_solitaire/card_generator/card_generator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Author: jeFF0Falltrades 4 | # 5 | # From the "Reverse Engineering and Weaponizing XP Solitaire" tutorial: 6 | # GitHub: https://github.com/jeFF0Falltrades/Tutorials/tree/master/hacking_weaponizing_solitaire 7 | # 8 | # YouTube: https://www.youtube.com/watch?v=ZmPArvsSii4 9 | # 10 | # Requires Pillow: 11 | # python3 -m pip install Pillow 12 | # 13 | # MIT License 14 | # 15 | # Copyright (c) 2022 Jeff Archer 16 | # 17 | # Permission is hereby granted, free of charge, to any person obtaining a copy 18 | # of this software and associated documentation files (the "Software"), to deal 19 | # in the Software without restriction, including without limitation the rights 20 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 21 | # copies of the Software, and to permit persons to whom the Software is 22 | # furnished to do so, subject to the following conditions: 23 | # 24 | # The above copyright notice and this permission notice shall be included in all 25 | # copies or substantial portions of the Software. 26 | # 27 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 28 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 29 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 30 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 31 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 32 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 33 | # SOFTWARE. 34 | 35 | from enum import Enum 36 | from PIL import Image, ImageDraw, ImageFont, ImageOps 37 | from sys import argv 38 | 39 | # This palette is used to use 'P' (palette) mode with Pillow below. 40 | # 41 | # This brings our bit depth down to just 8 bpp per image, which is not as low 42 | # as XP solitaire's images, but is the lowest Pillow supports, so it can help 43 | # avoid "Out of Memory" errors by keeping our images smaller. 44 | # 45 | # The palette uses [r,g,b,r,g,b...] format, so respectively, this palette is: 46 | # [black, red, white] 47 | COLOR_PALETTE = [0, 0, 0, 255, 0, 0, 255, 255, 255] 48 | 49 | 50 | # An enum class for indexing into COLOR_PALETTE 51 | class Color(Enum): 52 | BLACK = 0 53 | RED = 1 54 | WHITE = 2 55 | 56 | 57 | # Helper class to keep track of data per card suit 58 | # idx: Preserved ordering of suits as they appear in cards.dll 59 | # symbol: Unicode character of card suit 60 | # color: Color of card suit 61 | # abbr: Abbreviation of card suit name 62 | class CardSuits(Enum): 63 | 64 | def __init__(self, idx, symbol, color, abbr): 65 | self.idx = idx 66 | self.symbol = symbol 67 | self.color = color 68 | self.abbr = abbr 69 | 70 | CLUB = (0, '\u2663', Color.BLACK.value, 'C') 71 | DIAMOND = (1, '\u2666', Color.RED.value, 'D') 72 | HEART = (2, '\u2665', Color.RED.value, 'H') 73 | SPADE = (3, '\u2660', Color.BLACK.value, 'S') 74 | 75 | 76 | # Class representing a single card 77 | class Card: 78 | # Defaults determined by cards.dll 79 | CARD_WIDTH = 71 80 | CARD_HEIGHT = 96 81 | CARD_COLOR = Color.WHITE.value 82 | # Add a 1-pixel border of the card's color 83 | CARD_BORDER = (1, 1, 1, 1) 84 | 85 | FONT = "arial" 86 | FONT_SIZE = 20 87 | 88 | # x,y coordinates of the card character symbols in the two opposite 89 | # corners of the card, as well as the suit symbol in the center 90 | UPPER_LEFT = (5, 0) 91 | BOTTOM_RIGHT = (CARD_WIDTH - 17, CARD_HEIGHT - 22) 92 | CENTER = ((CARD_WIDTH / 2) - 5, (CARD_HEIGHT / 2) - 10) 93 | 94 | def __init__(self, 95 | character=None, 96 | id=None, 97 | image=None, 98 | suit=None, 99 | file_path=None): 100 | self.character = character 101 | self.id = self.character if id is None else id 102 | self.image = image 103 | self.suit = suit 104 | self.file_path = file_path 105 | 106 | # This function is commented out in the main program below, but can be used 107 | # to show the image right as it is generated 108 | def show(self): 109 | self.image.show() 110 | 111 | # Writes the image to a BMP file with character and suit (e.g. 3_H.bmp for 112 | # the 3 of Hearts) 113 | def write_file(self): 114 | self.file_path = '{}_{}.bmp'.format(self.character, self.suit.abbr) 115 | self.image.save(self.file_path, bitmap_format='bmp') 116 | 117 | 118 | # Gets the ID of the selected card, i.e. which BMP slot it will appear in within 119 | # cards.dll 120 | def get_id(desired_char, desired_suit): 121 | DEFAULT_IDS = {'K': 13, 'Q': 12, 'J': 11, '10': 10, 'A': 1} 122 | 123 | desired_id = DEFAULT_IDS['K'] 124 | 125 | # If it is not a face card or a 10, just subtract it from 10 to find the 126 | # ID (e.g. (ID of 9) == (ID of 10) - 1) 127 | if desired_char not in DEFAULT_IDS: 128 | desired_id = DEFAULT_IDS['10'] - (10 - int(desired_char)) 129 | else: 130 | desired_id = DEFAULT_IDS[desired_char] 131 | 132 | # Add 13 for each suit we have to traverse to get to the desired suit 133 | desired_id += 13 * desired_suit.idx 134 | return desired_id 135 | 136 | 137 | # Given a string of text, a list of desired card characters, and a list of 138 | # desired suits, generates 1 card for each character of the text, assigned the 139 | # suit and character given by its index in the character and suit lists 140 | def generate_cards(text='K', desired_char_list=None, desired_suit_list=None): 141 | DEFAULT_CHAR_LIST = [ 142 | 'A', 'K', 'Q', 'J', '10', '9', '8', '7', '6', '5', '4', '3', '2' 143 | ] 144 | char_list = DEFAULT_CHAR_LIST if ( 145 | desired_char_list is None 146 | or len(desired_char_list) == 0) else desired_char_list 147 | 148 | DEFAULT_SUIT_LIST = [suit for suit in CardSuits] 149 | suit_list = DEFAULT_SUIT_LIST if ( 150 | desired_suit_list is None 151 | or len(desired_suit_list) == 0) else desired_suit_list 152 | 153 | cards = [] 154 | for idx, character in enumerate(text.upper()): 155 | # Use modulo here to restart at beginning of suit list if we have 156 | # more characters than suits 157 | cur_suit = suit_list[idx % len(suit_list)] 158 | 159 | # We do not use modulo with the character list, because we will 160 | # overwrite already-written characters 161 | desired_id = get_id(char_list[idx], cur_suit) 162 | 163 | # Create a new Pillow Image in palette (P) mode 164 | card_image = Image.new('P', (Card.CARD_WIDTH, Card.CARD_HEIGHT), 165 | color=Card.CARD_COLOR) 166 | # We have to create the Image before assigning it a palette, so now 167 | # we go ahead and assign it (the previous assigned color will be mapped 168 | # to the new palette) 169 | card_image.putpalette(COLOR_PALETTE) 170 | font = ImageFont.truetype(f'{Card.FONT}.ttf', Card.FONT_SIZE) 171 | draw = ImageDraw.Draw(card_image) 172 | draw.text(Card.UPPER_LEFT, character, cur_suit.color, font=font) 173 | draw.text(Card.BOTTOM_RIGHT, character, cur_suit.color, font=font) 174 | draw.text(Card.CENTER, cur_suit.symbol, cur_suit.color, font=font) 175 | card_image = ImageOps.expand(card_image, 176 | border=Card.CARD_BORDER, 177 | fill=cur_suit.color) 178 | 179 | # Add the new Card to our list of cards 180 | cards.append(Card(character, desired_id, card_image, cur_suit)) 181 | return cards 182 | 183 | 184 | # Generates the Resource file (.rc) file that will be compiled to our RES file 185 | # (e.g. windres cards.rc -o cards.res) 186 | def generate_rc_file(cards): 187 | with open('cards.rc', 'w', encoding='utf-8') as o: 188 | for card in cards: 189 | o.write(f'{card.id} BITMAP "{card.file_path}"\n') 190 | 191 | 192 | # Sample input 193 | # Note that if all the cards need to be one character (like K), you can use 194 | # 195 | # chars = ['K' * len(text)] 196 | # 197 | # and for all cards to be one suit, you can just use 198 | # 199 | # suits = ['D'] as suits will be applied with modulus ops to the cards 200 | text = 'jeFF0Falltrades' 201 | chars = [ 202 | 'A', 'A', 'A', 'A', 'K', 'Q', 'J', '10', '9', '8', '7', '6', '5', '4', '3' 203 | ] 204 | suits = [] 205 | 206 | # Use the text specified via command line, if given, else use the hardcoded text 207 | if len(argv) > 1: 208 | if argv[1] in ['-h', '--help']: 209 | print( 210 | 'Usage: card_generator.py\nEdit script to select text, characters, and suits' 211 | ) 212 | raise SystemExit(0) 213 | text = argv[1] 214 | 215 | # Generate and save cards, then generate the .rc file 216 | cards = generate_cards(text, chars, suits) 217 | for card in cards: 218 | # card.show() 219 | card.write_file() 220 | generate_rc_file(cards) 221 | -------------------------------------------------------------------------------- /hacking_weaponizing_solitaire/card_generator/requirements.txt: -------------------------------------------------------------------------------- 1 | Pillow -------------------------------------------------------------------------------- /hacking_weaponizing_solitaire/cards_dll_proxy/cards.def: -------------------------------------------------------------------------------- 1 | EXPORTS 2 | cdtDraw=cards_original.dll.cdtDraw @3 3 | cdtDrawExt=cards_original.dll.cdtDrawExt @4 4 | cdtAnimate=cards_original.dll.cdtAnimate @2 5 | cdtTerm=cards_original.dll.cdtTerm @6 -------------------------------------------------------------------------------- /hacking_weaponizing_solitaire/cards_dll_proxy/cards_proxy.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Author: jeFF0Falltrades 3 | 4 | From the "Reverse Engineering and Weaponizing XP Solitaire" tutorial: 5 | GitHub: https://github.com/jeFF0Falltrades/Tutorials/tree/master/hacking_weaponizing_solitaire 6 | 7 | YouTube: https://www.youtube.com/watch?v=ZmPArvsSii4 8 | 9 | MIT License 10 | 11 | Copyright (c) 2022 Jeff Archer 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a copy 14 | of this software and associated documentation files (the "Software"), to deal 15 | in the Software without restriction, including without limitation the rights 16 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | copies of the Software, and to permit persons to whom the Software is 18 | furnished to do so, subject to the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be included in all 21 | copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | SOFTWARE. 30 | */ 31 | #include 32 | #include 33 | 34 | #define EXTERN_DLL_EXPORT extern "C" __declspec(dllexport) 35 | 36 | // Function signature taken from cards.dll 37 | typedef BOOL(WINAPI *cdtInitPtr)(int *width, int *height); 38 | 39 | EXTERN_DLL_EXPORT BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) 40 | { 41 | { 42 | switch (ul_reason_for_call) 43 | { 44 | case DLL_PROCESS_ATTACH: 45 | case DLL_THREAD_ATTACH: 46 | case DLL_THREAD_DETACH: 47 | case DLL_PROCESS_DETACH: 48 | break; 49 | } 50 | return TRUE; 51 | } 52 | } 53 | 54 | EXTERN_DLL_EXPORT BOOL cdtInit(int *width, int *height) 55 | { 56 | HINSTANCE loadedDLL = LoadLibrary(TEXT("cards_original.dll")); 57 | if (!loadedDLL) 58 | { 59 | printf("Format message failed with 0x%x\n", GetLastError()); 60 | return FALSE; 61 | } 62 | cdtInitPtr proxied_function = (cdtInitPtr)GetProcAddress(loadedDLL, "cdtInit"); 63 | if (!proxied_function) 64 | { 65 | return FALSE; 66 | } 67 | /***** Start of Weaponized Code *****/ 68 | system("start \"\" \"https://media.tenor.com/x8v1oNUOmg4AAAAd/rickroll-roll.gif\""); 69 | /***** End of Weaponized Code *****/ 70 | 71 | // Proxy call to the legitimate cdtInit 72 | BOOL result = proxied_function(width, height); 73 | FreeLibrary(loadedDLL); 74 | return result; 75 | } 76 | -------------------------------------------------------------------------------- /hacking_weaponizing_solitaire/example_files/0_C.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeFF0Falltrades/Tutorials/c9347157e74f914353ba793c9184780524dd67f6/hacking_weaponizing_solitaire/example_files/0_C.bmp -------------------------------------------------------------------------------- /hacking_weaponizing_solitaire/example_files/A_H.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeFF0Falltrades/Tutorials/c9347157e74f914353ba793c9184780524dd67f6/hacking_weaponizing_solitaire/example_files/A_H.bmp -------------------------------------------------------------------------------- /hacking_weaponizing_solitaire/example_files/A_S.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeFF0Falltrades/Tutorials/c9347157e74f914353ba793c9184780524dd67f6/hacking_weaponizing_solitaire/example_files/A_S.bmp -------------------------------------------------------------------------------- /hacking_weaponizing_solitaire/example_files/D_C.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeFF0Falltrades/Tutorials/c9347157e74f914353ba793c9184780524dd67f6/hacking_weaponizing_solitaire/example_files/D_C.bmp -------------------------------------------------------------------------------- /hacking_weaponizing_solitaire/example_files/E_D.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeFF0Falltrades/Tutorials/c9347157e74f914353ba793c9184780524dd67f6/hacking_weaponizing_solitaire/example_files/E_D.bmp -------------------------------------------------------------------------------- /hacking_weaponizing_solitaire/example_files/F_D.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeFF0Falltrades/Tutorials/c9347157e74f914353ba793c9184780524dd67f6/hacking_weaponizing_solitaire/example_files/F_D.bmp -------------------------------------------------------------------------------- /hacking_weaponizing_solitaire/example_files/F_H.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeFF0Falltrades/Tutorials/c9347157e74f914353ba793c9184780524dd67f6/hacking_weaponizing_solitaire/example_files/F_H.bmp -------------------------------------------------------------------------------- /hacking_weaponizing_solitaire/example_files/F_S.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeFF0Falltrades/Tutorials/c9347157e74f914353ba793c9184780524dd67f6/hacking_weaponizing_solitaire/example_files/F_S.bmp -------------------------------------------------------------------------------- /hacking_weaponizing_solitaire/example_files/J_C.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeFF0Falltrades/Tutorials/c9347157e74f914353ba793c9184780524dd67f6/hacking_weaponizing_solitaire/example_files/J_C.bmp -------------------------------------------------------------------------------- /hacking_weaponizing_solitaire/example_files/L_C.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeFF0Falltrades/Tutorials/c9347157e74f914353ba793c9184780524dd67f6/hacking_weaponizing_solitaire/example_files/L_C.bmp -------------------------------------------------------------------------------- /hacking_weaponizing_solitaire/example_files/L_S.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeFF0Falltrades/Tutorials/c9347157e74f914353ba793c9184780524dd67f6/hacking_weaponizing_solitaire/example_files/L_S.bmp -------------------------------------------------------------------------------- /hacking_weaponizing_solitaire/example_files/R_H.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeFF0Falltrades/Tutorials/c9347157e74f914353ba793c9184780524dd67f6/hacking_weaponizing_solitaire/example_files/R_H.bmp -------------------------------------------------------------------------------- /hacking_weaponizing_solitaire/example_files/S_H.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeFF0Falltrades/Tutorials/c9347157e74f914353ba793c9184780524dd67f6/hacking_weaponizing_solitaire/example_files/S_H.bmp -------------------------------------------------------------------------------- /hacking_weaponizing_solitaire/example_files/T_D.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeFF0Falltrades/Tutorials/c9347157e74f914353ba793c9184780524dd67f6/hacking_weaponizing_solitaire/example_files/T_D.bmp -------------------------------------------------------------------------------- /hacking_weaponizing_solitaire/example_files/cards.rc: -------------------------------------------------------------------------------- 1 | 1 BITMAP "J_C.bmp" 2 | 14 BITMAP "E_D.bmp" 3 | 27 BITMAP "F_H.bmp" 4 | 40 BITMAP "F_S.bmp" 5 | 13 BITMAP "0_C.bmp" 6 | 25 BITMAP "F_D.bmp" 7 | 37 BITMAP "A_H.bmp" 8 | 49 BITMAP "L_S.bmp" 9 | 9 BITMAP "L_C.bmp" 10 | 21 BITMAP "T_D.bmp" 11 | 33 BITMAP "R_H.bmp" 12 | 45 BITMAP "A_S.bmp" 13 | 5 BITMAP "D_C.bmp" 14 | 17 BITMAP "E_D.bmp" 15 | 29 BITMAP "S_H.bmp" 16 | -------------------------------------------------------------------------------- /hacking_weaponizing_solitaire/example_files/cards.res: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeFF0Falltrades/Tutorials/c9347157e74f914353ba793c9184780524dd67f6/hacking_weaponizing_solitaire/example_files/cards.res -------------------------------------------------------------------------------- /hacking_weaponizing_solitaire/example_files/cards_weaponized.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeFF0Falltrades/Tutorials/c9347157e74f914353ba793c9184780524dd67f6/hacking_weaponizing_solitaire/example_files/cards_weaponized.dll -------------------------------------------------------------------------------- /master0Fnone_classes/1_x86_Demystified/README.md: -------------------------------------------------------------------------------- 1 | # Episode 1: x86 Assembly Demystified 2 | 3 | ## Class Objectives 4 | 5 | - Walk through the Language Processing System that converts high-level code to assembly code to the machine code read by your processor, and all of the stages in-between 6 | - Introduce several common features of x86/x64 assembly language and conventions 7 | - Walk through a practice program demonstrating several common C programming structures and statements 8 | - Reverse that practice program in Ghidra to practice identifying these structures and instructions 9 | - Challenge you to take what you've learned and get yourself onto the "Wall of Fame" by finding the hidden flag in the included "CrackMe" program! 10 | 11 | ## YouTube Videos 12 | 13 | - [Part 1](https://youtu.be/ZXoW5iqbFJE) 14 | - [Part 2](https://youtu.be/d-zxyLQlaYw) 15 | - [Part 3](https://youtu.be/g-8Vk7Jik9o) 16 | 17 | ## Directory List & Description 18 | 19 | ``` 20 | └───1_x86_Demystified 21 | │ README.md (you are here) 22 | │ x86_assembly_reversing_cheat_sheet.drawio (Cheat sheet reviewed in the episode) 23 | │ x86_assembly_reversing_cheat_sheet.pdf (Cheat sheet reviewed in the episode) 24 | │ 25 | ├───crackme 26 | │ crackme.zip (crackme challenge binary) 27 | │ Wall_of_Fame.md (crackme Wall of Fame) 28 | │ 29 | └───practice 30 | practice.c (Practice C program code) 31 | practice.zip (32- and 64-bit executables of practice.c) 32 | practice_assembly.s (practice.c assembly code) 33 | practice_compiled.o (practice.c object file) 34 | practice_machine_code.dmp (practice.c dump in binary) 35 | practice_preprocessed.c (practice.c after preprocessing) 36 | ``` 37 | 38 | ## Wall of Fame Submission Instructions 39 | 40 | 1. After disassembly/debugging/ripping apart the CrackMe program and finding the flag, go to [this form](https://forms.gle/XWWqYyeNUkFH8tHMA) 41 | 1. Fill in your name, the flag, and the challenge 42 | 1. Check back after several minutes, and the Wall of Fame file in the `crackme` folder should be updated with your name 43 | 1. Brag to your friends by showing them your name on the Wall of Fame, and find out how good your relationship with those friends is 44 | 45 | ## Let Me Know What You Think, and Share with Those Who Yearn to Learn! 46 | 47 | Enjoy and please leave questions and feedback on [YouTube](https://www.youtube.com/@jeff0falltrades) or [Mastodon](https://infosec.exchange/@jeFF0Falltrades)! 48 | -------------------------------------------------------------------------------- /master0Fnone_classes/1_x86_Demystified/crackme/Wall_of_Fame.md: -------------------------------------------------------------------------------- 1 | # Wall of Fame: x86 Assembly Demystified 2 | 3 | ## Congratulations to these awesome reversers who successfully found the flag! 4 | 5 | ``` 6 | jeFF0Falltrades (disqualified for cheating; pending movement to Wall of Shame) - 22MAR2023 7 | crucifix - 23-MAR-2023 8 | tracktwo - 23-MAR-2023 9 | cheez3d - 23-MAR-2023 10 | deathbyknowledge - 24-MAR-2023 11 | 0xca7 - 25-MAR-2023 12 | Tzion - 25-MAR-2023 13 | Moshe - 26-MAR-2023 14 | dotASM - 26-MAR-2023 15 | thecrowstudios - 27-MAR-2023 16 | firstbirth - 28-MAR-2023 17 | LainPoster - 28-MAR-2023 18 | lum8rjack - 28-MAR-2023 19 | szabiyako - 28-MAR-2023 20 | DefinitelyNotPkt - 29-MAR-2023 21 | Elvis - 29-MAR-2023 22 | maximxls - 29-MAR-2023 23 | mBRS_ - 01-APR-2023 24 | JWAM - 02-APR-2023 25 | Gianby - 02-APR-2023 26 | widberg - 03-APR-2023 27 | Akosai - 04-APR-2023 28 | t0st - 04-APR-2023 29 | Zyn - 07-APR-2023 30 | bixarrio - 11-APR-2023 31 | AlephNull - 13-APR-2023 32 | vikikomori - 14-APR-2023 33 | m4ra - 16-APR-2023 34 | hashdmp - 18-APR-2023 35 | UrandomJoe - 18-APR-2023 36 | M0uhab-0xRE - 18-APR-2023 37 | NightQuest - 19-APR-2023 (Write-Up: https://subtle.ninja/Tech/Reverse+Engineering/jeFF0Falltrade's+x86+Demystified) 38 | ItsProfessional - 25-APR-2023 39 | W_Louis - 27-APR-2023 40 | pkats15 - 30-APR-2023 41 | isThisWorking - 16-MAY-2023 42 | WelfareWarrior - 20-MAY-2023 43 | DodoBirb - 25-MAY-2023 44 | JohnWick_16 - 06-JUN-2023 45 | Alawapr - 09-JUN-2023 46 | ConsoleJockey - 12-JUL-2023 47 | drockasaurusrex - 17-JUL-2023 48 | Overload - 27-JUL-2023 49 | Promikron - 02-AUG-2023 50 | wRsTillHeReAlhamdulillah - 08-AUG-2023 51 | koolaidxk1d - 10-AUG-2023 52 | dundorma - 15-AUG-2023 53 | Cuco - 15-AUG-2023 54 | Ormiz - 19-AUG-2023 55 | MeowFeeder - 20-AUG-2023 56 | logolyte - 25-AUG-2023 57 | Isaac - 02-SEP-2023 58 | theNole - 06-SEP-2023 59 | dhruv - 12-SEP-2023 60 | rumblingturtle - 17-SEP-2023 61 | The cheater - 21-SEP-2023 62 | Kieran - 13-OCT-2023 63 | TheWolf216 - 18-OCT-2023 64 | gabejabes - 20-OCT-2023 65 | Shredder_22 - 22-OCT-2023 66 | Saiyan_Sai - 26-OCT-2023 67 | 4n3c3d - 27-OCT-2023 68 | neverwasd - 30-NOV-2023 (Write-Up: https://medium.com/@neverwasd/cracking-jeff0falltrades-master0fnone-class-de9848e67375) 69 | Ghost - 12-DEC-2023 70 | Marco G - 01-JAN-2024 71 | pugachev - 02-JAN-2024 72 | ClarkIV - 04-JAN-2024 (Write-Up: https://clarkiv.dev/posts/jeFF0Falltrades-crackme/) 73 | Dander - 28-FEB-2024 74 | Orionexe - 14-MAR-2024 75 | kauschie - 03-APR-2024 76 | paulintd2 - 12-APR-2024 77 | brutus - 25-APR-2024 78 | Gaspa79 - 01-MAY-2024 79 | Jarvx - 04-MAY-2024 80 | ABloodyDemon - 11-MAY-2024 81 | johnnyboy2329 - Thank you very much - 30-MAY-2024 82 | Evan Franzman - 31-MAY-2024 83 | DZ-__-DZ - 31-MAY-2024 84 | JPinto94 - 21-JUL-2024 85 | ab4y - 27-JUL-2024 86 | Somenameidk - 01-AUG-2024 87 | Mideno - 07-AUG-2024 88 | EuZeNinguem - 24-AUG-2024 89 | mgcr - 30-AUG-2024 90 | SkilluDev - 27-SEP-2024 91 | johnnnathan - 21-NOV-2024 92 | K0enM - 24-NOV-2024 93 | HarukoCinder - 30-DEC-2024 94 | JoshuaKlassen - 27-APR-2025 95 | nodiuus - 12-MAY-2025 96 | kmschr - 23-MAY-2025 97 | ``` 98 | -------------------------------------------------------------------------------- /master0Fnone_classes/1_x86_Demystified/crackme/crackme.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeFF0Falltrades/Tutorials/c9347157e74f914353ba793c9184780524dd67f6/master0Fnone_classes/1_x86_Demystified/crackme/crackme.zip -------------------------------------------------------------------------------- /master0Fnone_classes/1_x86_Demystified/practice/practice.c: -------------------------------------------------------------------------------- 1 | /* 2 | Author: jeFF0Falltrades 3 | 4 | From the master0Fnone Class Reverse Engineering Series: 5 | 6 | GitHub: https://github.com/jeFF0Falltrades/Tutorials/tree/master/master0Fnone_classes/1_x86_Demystified 7 | 8 | YouTube: https://youtu.be/ZXoW5iqbFJE 9 | 10 | MIT License 11 | 12 | Copyright (c) 2023 Jeff Archer 13 | 14 | Permission is hereby granted, free of charge, to any person obtaining a copy 15 | of this software and associated documentation files (the "Software"), to deal 16 | in the Software without restriction, including without limitation the rights 17 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 18 | copies of the Software, and to permit persons to whom the Software is 19 | furnished to do so, subject to the following conditions: 20 | 21 | The above copyright notice and this permission notice shall be included in all 22 | copies or substantial portions of the Software. 23 | 24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 29 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 30 | SOFTWARE. 31 | */ 32 | #include 33 | #include 34 | #include 35 | #define ARR_SIZE 10 36 | 37 | int global = 13; 38 | 39 | typedef struct thing 40 | { 41 | char c; 42 | int x; 43 | int *px; 44 | } Thing; 45 | 46 | typedef struct node 47 | { 48 | int data; 49 | struct node *next; 50 | } Node; 51 | 52 | void print_function_header(const char *function_name) 53 | { 54 | printf("****Running %s()****\n\n", function_name); 55 | } 56 | 57 | int quick_maths(const int x, const int y) 58 | { 59 | print_function_header("quick_maths"); 60 | int a = 3; 61 | int *b = (int *)malloc(sizeof(int)); 62 | if (!b) 63 | { 64 | printf("Uh-oh, no memory left!"); 65 | return 0; 66 | } 67 | *b = 5; 68 | char m = 'm'; 69 | 70 | printf("\t%d + %d = %d\n\n", x, y, x + y); 71 | printf("\t%d - %d = %d\n\n", x, y, x - y); 72 | printf("\t%d * %d = %d\n\n", x, y, x * y); 73 | printf("\t%d / %d = %d\n\n", x, y, x / y); 74 | printf("\t%d %% %d = %d\n\n", x, y, x % y); 75 | 76 | printf("\t%d << 2 = %d\n\n", a, a << 2); 77 | printf("\t%d >> 1 = %d\n\n", global, global >> 1); 78 | printf("\tChars can shift too!\n\t%c << 3 = %c\n\n", m, m << 3); 79 | 80 | printf("\t%d & 2 = %d\n\n", *b, *b & 2); 81 | printf("\t%d | 2 = %d\n\n", *b, *b | 2); 82 | printf("\t%d ^ %d = %d\n\n", a, *b, a ^ *b); 83 | 84 | printf("\t%d + 2 == %d is %d\n\n", a, *b, a + 2 == *b); 85 | printf("\t%d == %d is %d\n\n", a, *b, a == *b); 86 | printf("\t%d <= %d is %d\n\n", a, *b, a <= *b); 87 | 88 | free(b); 89 | b = NULL; 90 | 91 | return y - x; 92 | } 93 | 94 | void loop_soup() 95 | { 96 | print_function_header("loop_soup"); 97 | int x = 1; 98 | while (x < 3) 99 | { 100 | printf("\tx is %d\n\n", x++); 101 | } 102 | 103 | for (x = 0; x <= 10; x++) 104 | { 105 | printf("\tNow x is %d\n\n", x); 106 | } 107 | 108 | do 109 | { 110 | printf("\tFinally, x is %d\n\n", ++x); 111 | } while (x <= 14); 112 | while (true) 113 | { 114 | printf("\tI'm in an infinite loop\n\n"); 115 | x++; 116 | if (x != 17) 117 | { 118 | continue; 119 | } 120 | else 121 | { 122 | printf("\tx is 17 now; Time to break\n\n"); 123 | break; 124 | } 125 | } 126 | } 127 | 128 | void terms_and_conditions() 129 | { 130 | print_function_header("terms_and_conditions"); 131 | bool a = true, b = false; 132 | printf("\ta is %s\n\n", a ? "true" : "false"); 133 | a = b; 134 | printf("\tOh, actually, a is %s\n\n", a ? "true" : "false"); 135 | a = true; 136 | printf("\tWait, no, a is seriously %s\n\n", a ? "true" : "false"); 137 | printf("\ta && b is %s\n\n", a && b ? "true" : "false"); 138 | printf("\ta || b is %s\n\n", a || b ? "true" : "false"); 139 | 140 | printf("\tBe careful of order of operations!\n\n"); 141 | int c = 4, d = 6; 142 | printf("\t%d & %d != 0 is %s\n\n", c, d, c & d != 0 ? "true" : "false"); 143 | printf("\t(%d & %d) != 0 is %s\n\n", c, d, (c & d) != 0 ? "true" : "false"); 144 | 145 | if (a) 146 | { 147 | printf("\tIf you see this, a is true\n\n"); 148 | } 149 | if (!b) 150 | { 151 | printf("\tIf you see this, b is false\n\n"); 152 | } 153 | else if (b) 154 | { 155 | printf("\tIf you see this, b is true\n\n"); 156 | } 157 | 158 | if (a) 159 | { 160 | if (!b) 161 | { 162 | printf("\tIf you see this, a is true, and b is false\n\n"); 163 | } 164 | else 165 | { 166 | printf("\tIf you see this, a is true, and b is true\n\n"); 167 | } 168 | } 169 | 170 | if (a != b) 171 | { 172 | printf("\tIf you see this, a != b\n\n"); 173 | } 174 | 175 | int x = 2; 176 | switch (x) 177 | { 178 | case 1: 179 | x += 1; 180 | break; 181 | case 2: 182 | x *= 2; 183 | printf("\tx doubled to %d\n\n", x); 184 | break; 185 | case 3: 186 | x /= 2; 187 | break; 188 | case 4: 189 | x -= 2; 190 | break; 191 | case 5: 192 | default: 193 | printf("\tNEVER SHOULD HAVE COME HERE!\n\n"); 194 | } 195 | } 196 | 197 | void disarray(const int *passed_arr, const int passed_arr_size) 198 | { 199 | print_function_header("disarray"); 200 | int local_arr[ARR_SIZE]; 201 | for (int i = 0; i < ARR_SIZE; i++) 202 | { 203 | printf("\tSetting array[%d] to %d\n\n", i, i + 1); 204 | local_arr[i] = i + 1; 205 | } 206 | if (passed_arr_size == ARR_SIZE) 207 | { 208 | for (int j = 0; j < passed_arr_size; j++) 209 | { 210 | if (passed_arr[j] == local_arr[j]) 211 | { 212 | printf("\tpassed_arr[%d] == local_arr[%d] == %d\n\n", j, j, passed_arr[j]); 213 | } 214 | } 215 | } 216 | int *dynamic_arr = malloc(ARR_SIZE * sizeof(int)); 217 | if (!dynamic_arr) 218 | { 219 | printf("Uh-oh, no memory left!"); 220 | return; 221 | } 222 | printf("\tdynamic_arr has a size of %d\n\n", ARR_SIZE); 223 | int *temp_arr = realloc(dynamic_arr, ARR_SIZE * 2 * sizeof(int)); 224 | if (!temp_arr) 225 | { 226 | printf("Uh-oh, no memory left!"); 227 | free(dynamic_arr); 228 | dynamic_arr = NULL; 229 | return; 230 | } 231 | dynamic_arr = temp_arr; 232 | printf("\tNow, dynamic_arr has a size of %d\n\n", ARR_SIZE * 2); 233 | free(dynamic_arr); 234 | dynamic_arr = NULL; 235 | } 236 | 237 | void disappointers(char *str, const int str_size) 238 | { 239 | print_function_header("disappointers"); 240 | char *str_ptr = str; 241 | printf("\t"); 242 | for (; *str_ptr; str_ptr++) 243 | { 244 | printf("%c", *str_ptr); 245 | } 246 | str_ptr = NULL; 247 | str[str_size - 2] = '?'; 248 | printf("\n\t%s\n", str); 249 | } 250 | 251 | void struct_your_stuff() 252 | { 253 | print_function_header("struct_your_stuff"); 254 | Thing thing; 255 | thing.c = 'X'; 256 | thing.x = 13; 257 | thing.px = &(thing.x); 258 | 259 | Thing thing_two; 260 | thing_two.c = 'O'; 261 | thing_two.x = 7; 262 | thing_two.px = &(thing.x); 263 | 264 | thing.x = 12; 265 | printf("\tthing_two.x == %d\n\n", thing_two.x); 266 | printf("\t*thing_two.px == %d\n\n", *(thing_two.px)); 267 | } 268 | 269 | Node *insert_at_start_of_ll(Node *next, int data) 270 | { 271 | Node *new_node = (Node *)malloc(sizeof(Node)); 272 | if (!new_node) 273 | { 274 | printf("Uh-oh, no memory left!"); 275 | return NULL; 276 | } 277 | new_node->data = data; 278 | new_node->next = next; 279 | return new_node; 280 | } 281 | 282 | void linked_list_not_to_be_confused_with_zeldad_list() 283 | { 284 | print_function_header("linked_list_not_to_be_confused_with_zeldad_list"); 285 | Node *head = NULL; 286 | head = insert_at_start_of_ll(head, 99); 287 | head = insert_at_start_of_ll(head, 66); 288 | head = insert_at_start_of_ll(head, 33); 289 | 290 | Node *current = head; 291 | printf("\tHEAD =>"); 292 | while (current) 293 | { 294 | printf(" %d =>", current->data); 295 | current = current->next; 296 | } 297 | printf(" END\n\n"); 298 | 299 | current = head; 300 | Node *to_delete = NULL; 301 | while (current) 302 | { 303 | to_delete = current; 304 | current = current->next; 305 | printf("\tDeleting %d...\n\n", to_delete->data); 306 | free(to_delete); 307 | to_delete = NULL; 308 | } 309 | head = current = NULL; 310 | } 311 | 312 | int main() 313 | { 314 | print_function_header("main"); 315 | int x = 5, y = 7, result = 0; 316 | int arr[ARR_SIZE]; 317 | for (int i = 0; i < ARR_SIZE; i++) 318 | { 319 | arr[i] = i + 1; 320 | } 321 | char str[] = "I'm a string, look at me!\n"; 322 | int str_size = 0; 323 | for (str_size = 0; str[str_size] != '\0'; ++str_size) 324 | ; 325 | 326 | printf("quick_maths() result is %d\n\n", quick_maths(x, y)); 327 | loop_soup(); 328 | terms_and_conditions(); 329 | disarray(arr, ARR_SIZE); 330 | disappointers(str, str_size); 331 | struct_your_stuff(); 332 | linked_list_not_to_be_confused_with_zeldad_list(); 333 | 334 | printf("All done!\n\n"); 335 | getchar(); 336 | return 0; 337 | } -------------------------------------------------------------------------------- /master0Fnone_classes/1_x86_Demystified/practice/practice.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeFF0Falltrades/Tutorials/c9347157e74f914353ba793c9184780524dd67f6/master0Fnone_classes/1_x86_Demystified/practice/practice.zip -------------------------------------------------------------------------------- /master0Fnone_classes/1_x86_Demystified/practice/practice_assembly.s: -------------------------------------------------------------------------------- 1 | .file "practice.c" 2 | .text 3 | .def _printf; .scl 3; .type 32; .endef 4 | _printf: 5 | LFB8: 6 | .cfi_startproc 7 | pushl %ebp 8 | .cfi_def_cfa_offset 8 9 | .cfi_offset 5, -8 10 | movl %esp, %ebp 11 | .cfi_def_cfa_register 5 12 | pushl %ebx 13 | subl $36, %esp 14 | .cfi_offset 3, -12 15 | leal 12(%ebp), %eax 16 | movl %eax, -16(%ebp) 17 | movl -16(%ebp), %ebx 18 | movl $1, (%esp) 19 | movl __imp____acrt_iob_func, %eax 20 | call *%eax 21 | movl %ebx, 8(%esp) 22 | movl 8(%ebp), %edx 23 | movl %edx, 4(%esp) 24 | movl %eax, (%esp) 25 | call ___mingw_vfprintf 26 | movl %eax, -12(%ebp) 27 | movl -12(%ebp), %eax 28 | movl -4(%ebp), %ebx 29 | leave 30 | .cfi_restore 5 31 | .cfi_restore 3 32 | .cfi_def_cfa 4, 4 33 | ret 34 | .cfi_endproc 35 | LFE8: 36 | .globl _global 37 | .data 38 | .align 4 39 | _global: 40 | .long 13 41 | .section .rdata,"dr" 42 | LC0: 43 | .ascii "****Running %s()****\12\12\0" 44 | .text 45 | .globl _print_function_header 46 | .def _print_function_header; .scl 2; .type 32; .endef 47 | _print_function_header: 48 | LFB38: 49 | .cfi_startproc 50 | pushl %ebp 51 | .cfi_def_cfa_offset 8 52 | .cfi_offset 5, -8 53 | movl %esp, %ebp 54 | .cfi_def_cfa_register 5 55 | subl $24, %esp 56 | movl 8(%ebp), %eax 57 | movl %eax, 4(%esp) 58 | movl $LC0, (%esp) 59 | call _printf 60 | nop 61 | leave 62 | .cfi_restore 5 63 | .cfi_def_cfa 4, 4 64 | ret 65 | .cfi_endproc 66 | LFE38: 67 | .section .rdata,"dr" 68 | LC1: 69 | .ascii "quick_maths\0" 70 | LC2: 71 | .ascii "Uh-oh, no memory left!\0" 72 | LC3: 73 | .ascii "\11%d + %d = %d\12\12\0" 74 | LC4: 75 | .ascii "\11%d - %d = %d\12\12\0" 76 | LC5: 77 | .ascii "\11%d * %d = %d\12\12\0" 78 | LC6: 79 | .ascii "\11%d / %d = %d\12\12\0" 80 | LC7: 81 | .ascii "\11%d %% %d = %d\12\12\0" 82 | LC8: 83 | .ascii "\11%d << 2 = %d\12\12\0" 84 | LC9: 85 | .ascii "\11%d >> 1 = %d\12\12\0" 86 | .align 4 87 | LC10: 88 | .ascii "\11Chars can shift too!\12\11%c << 3 = %c\12\12\0" 89 | LC11: 90 | .ascii "\11%d & 2 = %d\12\12\0" 91 | LC12: 92 | .ascii "\11%d | 2 = %d\12\12\0" 93 | LC13: 94 | .ascii "\11%d ^ %d = %d\12\12\0" 95 | LC14: 96 | .ascii "\11%d + 2 == %d is %d\12\12\0" 97 | LC15: 98 | .ascii "\11%d == %d is %d\12\12\0" 99 | LC16: 100 | .ascii "\11%d <= %d is %d\12\12\0" 101 | .text 102 | .globl _quick_maths 103 | .def _quick_maths; .scl 2; .type 32; .endef 104 | _quick_maths: 105 | LFB39: 106 | .cfi_startproc 107 | pushl %ebp 108 | .cfi_def_cfa_offset 8 109 | .cfi_offset 5, -8 110 | movl %esp, %ebp 111 | .cfi_def_cfa_register 5 112 | subl $40, %esp 113 | movl $LC1, (%esp) 114 | call _print_function_header 115 | movl $3, -12(%ebp) 116 | movl $4, (%esp) 117 | call _malloc 118 | movl %eax, -16(%ebp) 119 | cmpl $0, -16(%ebp) 120 | jne L5 121 | movl $LC2, (%esp) 122 | call _printf 123 | movl $0, %eax 124 | jmp L6 125 | L5: 126 | movl -16(%ebp), %eax 127 | movl $5, (%eax) 128 | movb $109, -17(%ebp) 129 | movl 8(%ebp), %edx 130 | movl 12(%ebp), %eax 131 | addl %edx, %eax 132 | movl %eax, 12(%esp) 133 | movl 12(%ebp), %eax 134 | movl %eax, 8(%esp) 135 | movl 8(%ebp), %eax 136 | movl %eax, 4(%esp) 137 | movl $LC3, (%esp) 138 | call _printf 139 | movl 8(%ebp), %eax 140 | subl 12(%ebp), %eax 141 | movl %eax, 12(%esp) 142 | movl 12(%ebp), %eax 143 | movl %eax, 8(%esp) 144 | movl 8(%ebp), %eax 145 | movl %eax, 4(%esp) 146 | movl $LC4, (%esp) 147 | call _printf 148 | movl 8(%ebp), %eax 149 | imull 12(%ebp), %eax 150 | movl %eax, 12(%esp) 151 | movl 12(%ebp), %eax 152 | movl %eax, 8(%esp) 153 | movl 8(%ebp), %eax 154 | movl %eax, 4(%esp) 155 | movl $LC5, (%esp) 156 | call _printf 157 | movl 8(%ebp), %eax 158 | cltd 159 | idivl 12(%ebp) 160 | movl %eax, 12(%esp) 161 | movl 12(%ebp), %eax 162 | movl %eax, 8(%esp) 163 | movl 8(%ebp), %eax 164 | movl %eax, 4(%esp) 165 | movl $LC6, (%esp) 166 | call _printf 167 | movl 8(%ebp), %eax 168 | cltd 169 | idivl 12(%ebp) 170 | movl %edx, %eax 171 | movl %eax, 12(%esp) 172 | movl 12(%ebp), %eax 173 | movl %eax, 8(%esp) 174 | movl 8(%ebp), %eax 175 | movl %eax, 4(%esp) 176 | movl $LC7, (%esp) 177 | call _printf 178 | movl -12(%ebp), %eax 179 | sall $2, %eax 180 | movl %eax, 8(%esp) 181 | movl -12(%ebp), %eax 182 | movl %eax, 4(%esp) 183 | movl $LC8, (%esp) 184 | call _printf 185 | movl _global, %eax 186 | sarl %eax 187 | movl %eax, %edx 188 | movl _global, %eax 189 | movl %edx, 8(%esp) 190 | movl %eax, 4(%esp) 191 | movl $LC9, (%esp) 192 | call _printf 193 | movsbl -17(%ebp), %eax 194 | leal 0(,%eax,8), %edx 195 | movsbl -17(%ebp), %eax 196 | movl %edx, 8(%esp) 197 | movl %eax, 4(%esp) 198 | movl $LC10, (%esp) 199 | call _printf 200 | movl -16(%ebp), %eax 201 | movl (%eax), %eax 202 | andl $2, %eax 203 | movl %eax, %edx 204 | movl -16(%ebp), %eax 205 | movl (%eax), %eax 206 | movl %edx, 8(%esp) 207 | movl %eax, 4(%esp) 208 | movl $LC11, (%esp) 209 | call _printf 210 | movl -16(%ebp), %eax 211 | movl (%eax), %eax 212 | orl $2, %eax 213 | movl %eax, %edx 214 | movl -16(%ebp), %eax 215 | movl (%eax), %eax 216 | movl %edx, 8(%esp) 217 | movl %eax, 4(%esp) 218 | movl $LC12, (%esp) 219 | call _printf 220 | movl -16(%ebp), %eax 221 | movl (%eax), %eax 222 | xorl -12(%ebp), %eax 223 | movl %eax, %edx 224 | movl -16(%ebp), %eax 225 | movl (%eax), %eax 226 | movl %edx, 12(%esp) 227 | movl %eax, 8(%esp) 228 | movl -12(%ebp), %eax 229 | movl %eax, 4(%esp) 230 | movl $LC13, (%esp) 231 | call _printf 232 | movl -12(%ebp), %eax 233 | leal 2(%eax), %edx 234 | movl -16(%ebp), %eax 235 | movl (%eax), %eax 236 | cmpl %eax, %edx 237 | sete %al 238 | movzbl %al, %edx 239 | movl -16(%ebp), %eax 240 | movl (%eax), %eax 241 | movl %edx, 12(%esp) 242 | movl %eax, 8(%esp) 243 | movl -12(%ebp), %eax 244 | movl %eax, 4(%esp) 245 | movl $LC14, (%esp) 246 | call _printf 247 | movl -16(%ebp), %eax 248 | movl (%eax), %eax 249 | cmpl %eax, -12(%ebp) 250 | sete %al 251 | movzbl %al, %edx 252 | movl -16(%ebp), %eax 253 | movl (%eax), %eax 254 | movl %edx, 12(%esp) 255 | movl %eax, 8(%esp) 256 | movl -12(%ebp), %eax 257 | movl %eax, 4(%esp) 258 | movl $LC15, (%esp) 259 | call _printf 260 | movl -16(%ebp), %eax 261 | movl (%eax), %eax 262 | cmpl %eax, -12(%ebp) 263 | setle %al 264 | movzbl %al, %edx 265 | movl -16(%ebp), %eax 266 | movl (%eax), %eax 267 | movl %edx, 12(%esp) 268 | movl %eax, 8(%esp) 269 | movl -12(%ebp), %eax 270 | movl %eax, 4(%esp) 271 | movl $LC16, (%esp) 272 | call _printf 273 | movl -16(%ebp), %eax 274 | movl %eax, (%esp) 275 | call _free 276 | movl $0, -16(%ebp) 277 | movl 12(%ebp), %eax 278 | subl 8(%ebp), %eax 279 | L6: 280 | leave 281 | .cfi_restore 5 282 | .cfi_def_cfa 4, 4 283 | ret 284 | .cfi_endproc 285 | LFE39: 286 | .section .rdata,"dr" 287 | LC17: 288 | .ascii "loop_soup\0" 289 | LC18: 290 | .ascii "\11x is %d\12\12\0" 291 | LC19: 292 | .ascii "\11Now x is %d\12\12\0" 293 | LC20: 294 | .ascii "\11Finally, x is %d\12\12\0" 295 | LC21: 296 | .ascii "\11I'm in an infinite loop\12\12\0" 297 | LC22: 298 | .ascii "\11x is 17 now; Time to break\12\12\0" 299 | .text 300 | .globl _loop_soup 301 | .def _loop_soup; .scl 2; .type 32; .endef 302 | _loop_soup: 303 | LFB40: 304 | .cfi_startproc 305 | pushl %ebp 306 | .cfi_def_cfa_offset 8 307 | .cfi_offset 5, -8 308 | movl %esp, %ebp 309 | .cfi_def_cfa_register 5 310 | subl $40, %esp 311 | movl $LC17, (%esp) 312 | call _print_function_header 313 | movl $1, -12(%ebp) 314 | jmp L8 315 | L9: 316 | movl -12(%ebp), %eax 317 | leal 1(%eax), %edx 318 | movl %edx, -12(%ebp) 319 | movl %eax, 4(%esp) 320 | movl $LC18, (%esp) 321 | call _printf 322 | L8: 323 | cmpl $2, -12(%ebp) 324 | jle L9 325 | movl $0, -12(%ebp) 326 | jmp L10 327 | L11: 328 | movl -12(%ebp), %eax 329 | movl %eax, 4(%esp) 330 | movl $LC19, (%esp) 331 | call _printf 332 | addl $1, -12(%ebp) 333 | L10: 334 | cmpl $10, -12(%ebp) 335 | jle L11 336 | L12: 337 | addl $1, -12(%ebp) 338 | movl -12(%ebp), %eax 339 | movl %eax, 4(%esp) 340 | movl $LC20, (%esp) 341 | call _printf 342 | cmpl $14, -12(%ebp) 343 | jle L12 344 | L16: 345 | movl $LC21, (%esp) 346 | call _printf 347 | addl $1, -12(%ebp) 348 | cmpl $17, -12(%ebp) 349 | jne L19 350 | movl $LC22, (%esp) 351 | call _printf 352 | jmp L18 353 | L19: 354 | nop 355 | jmp L16 356 | L18: 357 | nop 358 | leave 359 | .cfi_restore 5 360 | .cfi_def_cfa 4, 4 361 | ret 362 | .cfi_endproc 363 | LFE40: 364 | .section .rdata,"dr" 365 | LC23: 366 | .ascii "terms_and_conditions\0" 367 | LC24: 368 | .ascii "true\0" 369 | LC25: 370 | .ascii "false\0" 371 | LC26: 372 | .ascii "\11a is %s\12\12\0" 373 | LC27: 374 | .ascii "\11Oh, actually, a is %s\12\12\0" 375 | .align 4 376 | LC28: 377 | .ascii "\11Wait, no, a is seriously %s\12\12\0" 378 | LC29: 379 | .ascii "\11a && b is %s\12\12\0" 380 | LC30: 381 | .ascii "\11a || b is %s\12\12\0" 382 | .align 4 383 | LC31: 384 | .ascii "\11Be careful of order of operations!\12\12\0" 385 | LC32: 386 | .ascii "\11%d & %d != 0 is %s\12\12\0" 387 | LC33: 388 | .ascii "\11(%d & %d) != 0 is %s\12\12\0" 389 | LC34: 390 | .ascii "\11If you see this, a is true\12\12\0" 391 | .align 4 392 | LC35: 393 | .ascii "\11If you see this, b is false\12\12\0" 394 | LC36: 395 | .ascii "\11If you see this, b is true\12\12\0" 396 | .align 4 397 | LC37: 398 | .ascii "\11If you see this, a is true, and b is false\12\12\0" 399 | .align 4 400 | LC38: 401 | .ascii "\11If you see this, a is true, and b is true\12\12\0" 402 | LC39: 403 | .ascii "\11If you see this, a != b\12\12\0" 404 | LC40: 405 | .ascii "\11x doubled to %d\12\12\0" 406 | .align 4 407 | LC41: 408 | .ascii "\11NEVER SHOULD HAVE COME HERE!\12\12\0" 409 | .text 410 | .globl _terms_and_conditions 411 | .def _terms_and_conditions; .scl 2; .type 32; .endef 412 | _terms_and_conditions: 413 | LFB41: 414 | .cfi_startproc 415 | pushl %ebp 416 | .cfi_def_cfa_offset 8 417 | .cfi_offset 5, -8 418 | movl %esp, %ebp 419 | .cfi_def_cfa_register 5 420 | subl $40, %esp 421 | movl $LC23, (%esp) 422 | call _print_function_header 423 | movb $1, -9(%ebp) 424 | movb $0, -10(%ebp) 425 | cmpb $0, -9(%ebp) 426 | je L21 427 | movl $LC24, %eax 428 | jmp L22 429 | L21: 430 | movl $LC25, %eax 431 | L22: 432 | movl %eax, 4(%esp) 433 | movl $LC26, (%esp) 434 | call _printf 435 | movzbl -10(%ebp), %eax 436 | movb %al, -9(%ebp) 437 | cmpb $0, -9(%ebp) 438 | je L23 439 | movl $LC24, %eax 440 | jmp L24 441 | L23: 442 | movl $LC25, %eax 443 | L24: 444 | movl %eax, 4(%esp) 445 | movl $LC27, (%esp) 446 | call _printf 447 | movb $1, -9(%ebp) 448 | cmpb $0, -9(%ebp) 449 | je L25 450 | movl $LC24, %eax 451 | jmp L26 452 | L25: 453 | movl $LC25, %eax 454 | L26: 455 | movl %eax, 4(%esp) 456 | movl $LC28, (%esp) 457 | call _printf 458 | cmpb $0, -9(%ebp) 459 | je L27 460 | cmpb $0, -10(%ebp) 461 | je L27 462 | movl $LC24, %eax 463 | jmp L28 464 | L27: 465 | movl $LC25, %eax 466 | L28: 467 | movl %eax, 4(%esp) 468 | movl $LC29, (%esp) 469 | call _printf 470 | cmpb $0, -9(%ebp) 471 | jne L29 472 | cmpb $0, -10(%ebp) 473 | je L30 474 | L29: 475 | movl $LC24, %eax 476 | jmp L31 477 | L30: 478 | movl $LC25, %eax 479 | L31: 480 | movl %eax, 4(%esp) 481 | movl $LC30, (%esp) 482 | call _printf 483 | movl $LC31, (%esp) 484 | call _printf 485 | movl $4, -16(%ebp) 486 | movl $6, -20(%ebp) 487 | cmpl $0, -20(%ebp) 488 | setne %al 489 | movzbl %al, %eax 490 | andl -16(%ebp), %eax 491 | testl %eax, %eax 492 | je L32 493 | movl $LC24, %eax 494 | jmp L33 495 | L32: 496 | movl $LC25, %eax 497 | L33: 498 | movl %eax, 12(%esp) 499 | movl -20(%ebp), %eax 500 | movl %eax, 8(%esp) 501 | movl -16(%ebp), %eax 502 | movl %eax, 4(%esp) 503 | movl $LC32, (%esp) 504 | call _printf 505 | movl -16(%ebp), %eax 506 | andl -20(%ebp), %eax 507 | testl %eax, %eax 508 | je L34 509 | movl $LC24, %eax 510 | jmp L35 511 | L34: 512 | movl $LC25, %eax 513 | L35: 514 | movl %eax, 12(%esp) 515 | movl -20(%ebp), %eax 516 | movl %eax, 8(%esp) 517 | movl -16(%ebp), %eax 518 | movl %eax, 4(%esp) 519 | movl $LC33, (%esp) 520 | call _printf 521 | cmpb $0, -9(%ebp) 522 | je L36 523 | movl $LC34, (%esp) 524 | call _printf 525 | L36: 526 | movzbl -10(%ebp), %eax 527 | xorl $1, %eax 528 | testb %al, %al 529 | je L37 530 | movl $LC35, (%esp) 531 | call _printf 532 | jmp L38 533 | L37: 534 | cmpb $0, -10(%ebp) 535 | je L38 536 | movl $LC36, (%esp) 537 | call _printf 538 | L38: 539 | cmpb $0, -9(%ebp) 540 | je L39 541 | movzbl -10(%ebp), %eax 542 | xorl $1, %eax 543 | testb %al, %al 544 | je L40 545 | movl $LC37, (%esp) 546 | call _printf 547 | jmp L39 548 | L40: 549 | movl $LC38, (%esp) 550 | call _printf 551 | L39: 552 | movzbl -9(%ebp), %eax 553 | cmpb -10(%ebp), %al 554 | je L41 555 | movl $LC39, (%esp) 556 | call _printf 557 | L41: 558 | movl $2, -24(%ebp) 559 | cmpl $4, -24(%ebp) 560 | je L42 561 | cmpl $4, -24(%ebp) 562 | jg L43 563 | cmpl $3, -24(%ebp) 564 | je L44 565 | cmpl $3, -24(%ebp) 566 | jg L43 567 | cmpl $1, -24(%ebp) 568 | je L45 569 | cmpl $2, -24(%ebp) 570 | je L46 571 | jmp L43 572 | L45: 573 | addl $1, -24(%ebp) 574 | jmp L47 575 | L46: 576 | sall -24(%ebp) 577 | movl -24(%ebp), %eax 578 | movl %eax, 4(%esp) 579 | movl $LC40, (%esp) 580 | call _printf 581 | jmp L47 582 | L44: 583 | movl -24(%ebp), %eax 584 | movl %eax, %edx 585 | shrl $31, %edx 586 | addl %edx, %eax 587 | sarl %eax 588 | movl %eax, -24(%ebp) 589 | jmp L47 590 | L42: 591 | subl $2, -24(%ebp) 592 | jmp L47 593 | L43: 594 | movl $LC41, (%esp) 595 | call _printf 596 | nop 597 | L47: 598 | nop 599 | leave 600 | .cfi_restore 5 601 | .cfi_def_cfa 4, 4 602 | ret 603 | .cfi_endproc 604 | LFE41: 605 | .section .rdata,"dr" 606 | LC42: 607 | .ascii "disarray\0" 608 | LC43: 609 | .ascii "\11Setting array[%d] to %d\12\12\0" 610 | .align 4 611 | LC44: 612 | .ascii "\11passed_arr[%d] == local_arr[%d] == %d\12\12\0" 613 | .align 4 614 | LC45: 615 | .ascii "\11dynamic_arr has a size of %d\12\12\0" 616 | .align 4 617 | LC46: 618 | .ascii "\11Now, dynamic_arr has a size of %d\12\12\0" 619 | .text 620 | .globl _disarray 621 | .def _disarray; .scl 2; .type 32; .endef 622 | _disarray: 623 | LFB42: 624 | .cfi_startproc 625 | pushl %ebp 626 | .cfi_def_cfa_offset 8 627 | .cfi_offset 5, -8 628 | movl %esp, %ebp 629 | .cfi_def_cfa_register 5 630 | subl $88, %esp 631 | movl $LC42, (%esp) 632 | call _print_function_header 633 | movl $0, -12(%ebp) 634 | jmp L49 635 | L50: 636 | movl -12(%ebp), %eax 637 | addl $1, %eax 638 | movl %eax, 8(%esp) 639 | movl -12(%ebp), %eax 640 | movl %eax, 4(%esp) 641 | movl $LC43, (%esp) 642 | call _printf 643 | movl -12(%ebp), %eax 644 | leal 1(%eax), %edx 645 | movl -12(%ebp), %eax 646 | movl %edx, -64(%ebp,%eax,4) 647 | addl $1, -12(%ebp) 648 | L49: 649 | cmpl $9, -12(%ebp) 650 | jle L50 651 | cmpl $10, 12(%ebp) 652 | jne L51 653 | movl $0, -16(%ebp) 654 | jmp L52 655 | L54: 656 | movl -16(%ebp), %eax 657 | leal 0(,%eax,4), %edx 658 | movl 8(%ebp), %eax 659 | addl %edx, %eax 660 | movl (%eax), %edx 661 | movl -16(%ebp), %eax 662 | movl -64(%ebp,%eax,4), %eax 663 | cmpl %eax, %edx 664 | jne L53 665 | movl -16(%ebp), %eax 666 | leal 0(,%eax,4), %edx 667 | movl 8(%ebp), %eax 668 | addl %edx, %eax 669 | movl (%eax), %eax 670 | movl %eax, 12(%esp) 671 | movl -16(%ebp), %eax 672 | movl %eax, 8(%esp) 673 | movl -16(%ebp), %eax 674 | movl %eax, 4(%esp) 675 | movl $LC44, (%esp) 676 | call _printf 677 | L53: 678 | addl $1, -16(%ebp) 679 | L52: 680 | movl -16(%ebp), %eax 681 | cmpl 12(%ebp), %eax 682 | jl L54 683 | L51: 684 | movl $40, (%esp) 685 | call _malloc 686 | movl %eax, -20(%ebp) 687 | cmpl $0, -20(%ebp) 688 | jne L55 689 | movl $LC2, (%esp) 690 | call _printf 691 | jmp L48 692 | L55: 693 | movl $10, 4(%esp) 694 | movl $LC45, (%esp) 695 | call _printf 696 | movl $80, 4(%esp) 697 | movl -20(%ebp), %eax 698 | movl %eax, (%esp) 699 | call _realloc 700 | movl %eax, -24(%ebp) 701 | cmpl $0, -24(%ebp) 702 | jne L57 703 | movl $LC2, (%esp) 704 | call _printf 705 | movl -20(%ebp), %eax 706 | movl %eax, (%esp) 707 | call _free 708 | movl $0, -20(%ebp) 709 | jmp L48 710 | L57: 711 | movl -24(%ebp), %eax 712 | movl %eax, -20(%ebp) 713 | movl $20, 4(%esp) 714 | movl $LC46, (%esp) 715 | call _printf 716 | movl -20(%ebp), %eax 717 | movl %eax, (%esp) 718 | call _free 719 | movl $0, -20(%ebp) 720 | L48: 721 | leave 722 | .cfi_restore 5 723 | .cfi_def_cfa 4, 4 724 | ret 725 | .cfi_endproc 726 | LFE42: 727 | .section .rdata,"dr" 728 | LC47: 729 | .ascii "disappointers\0" 730 | LC48: 731 | .ascii "\11\0" 732 | LC49: 733 | .ascii "%c\0" 734 | LC50: 735 | .ascii "\12\11%s\12\0" 736 | .text 737 | .globl _disappointers 738 | .def _disappointers; .scl 2; .type 32; .endef 739 | _disappointers: 740 | LFB43: 741 | .cfi_startproc 742 | pushl %ebp 743 | .cfi_def_cfa_offset 8 744 | .cfi_offset 5, -8 745 | movl %esp, %ebp 746 | .cfi_def_cfa_register 5 747 | subl $40, %esp 748 | movl $LC47, (%esp) 749 | call _print_function_header 750 | movl 8(%ebp), %eax 751 | movl %eax, -12(%ebp) 752 | movl $LC48, (%esp) 753 | call _printf 754 | jmp L60 755 | L61: 756 | movl -12(%ebp), %eax 757 | movzbl (%eax), %eax 758 | movsbl %al, %eax 759 | movl %eax, 4(%esp) 760 | movl $LC49, (%esp) 761 | call _printf 762 | addl $1, -12(%ebp) 763 | L60: 764 | movl -12(%ebp), %eax 765 | movzbl (%eax), %eax 766 | testb %al, %al 767 | jne L61 768 | movl $0, -12(%ebp) 769 | movl 12(%ebp), %eax 770 | leal -2(%eax), %edx 771 | movl 8(%ebp), %eax 772 | addl %edx, %eax 773 | movb $63, (%eax) 774 | movl 8(%ebp), %eax 775 | movl %eax, 4(%esp) 776 | movl $LC50, (%esp) 777 | call _printf 778 | nop 779 | leave 780 | .cfi_restore 5 781 | .cfi_def_cfa 4, 4 782 | ret 783 | .cfi_endproc 784 | LFE43: 785 | .section .rdata,"dr" 786 | LC51: 787 | .ascii "struct_your_stuff\0" 788 | LC52: 789 | .ascii "\11thing_two.x == %d\12\12\0" 790 | LC53: 791 | .ascii "\11*thing_two.px == %d\12\12\0" 792 | .text 793 | .globl _struct_your_stuff 794 | .def _struct_your_stuff; .scl 2; .type 32; .endef 795 | _struct_your_stuff: 796 | LFB44: 797 | .cfi_startproc 798 | pushl %ebp 799 | .cfi_def_cfa_offset 8 800 | .cfi_offset 5, -8 801 | movl %esp, %ebp 802 | .cfi_def_cfa_register 5 803 | subl $56, %esp 804 | movl $LC51, (%esp) 805 | call _print_function_header 806 | movb $88, -20(%ebp) 807 | movl $13, -16(%ebp) 808 | leal -20(%ebp), %eax 809 | addl $4, %eax 810 | movl %eax, -12(%ebp) 811 | movb $79, -32(%ebp) 812 | movl $7, -28(%ebp) 813 | leal -20(%ebp), %eax 814 | addl $4, %eax 815 | movl %eax, -24(%ebp) 816 | movl $12, -16(%ebp) 817 | movl -28(%ebp), %eax 818 | movl %eax, 4(%esp) 819 | movl $LC52, (%esp) 820 | call _printf 821 | movl -24(%ebp), %eax 822 | movl (%eax), %eax 823 | movl %eax, 4(%esp) 824 | movl $LC53, (%esp) 825 | call _printf 826 | nop 827 | leave 828 | .cfi_restore 5 829 | .cfi_def_cfa 4, 4 830 | ret 831 | .cfi_endproc 832 | LFE44: 833 | .globl _insert_at_start_of_ll 834 | .def _insert_at_start_of_ll; .scl 2; .type 32; .endef 835 | _insert_at_start_of_ll: 836 | LFB45: 837 | .cfi_startproc 838 | pushl %ebp 839 | .cfi_def_cfa_offset 8 840 | .cfi_offset 5, -8 841 | movl %esp, %ebp 842 | .cfi_def_cfa_register 5 843 | subl $40, %esp 844 | movl $8, (%esp) 845 | call _malloc 846 | movl %eax, -12(%ebp) 847 | cmpl $0, -12(%ebp) 848 | jne L64 849 | movl $LC2, (%esp) 850 | call _printf 851 | movl $0, %eax 852 | jmp L65 853 | L64: 854 | movl -12(%ebp), %eax 855 | movl 12(%ebp), %edx 856 | movl %edx, (%eax) 857 | movl -12(%ebp), %eax 858 | movl 8(%ebp), %edx 859 | movl %edx, 4(%eax) 860 | movl -12(%ebp), %eax 861 | L65: 862 | leave 863 | .cfi_restore 5 864 | .cfi_def_cfa 4, 4 865 | ret 866 | .cfi_endproc 867 | LFE45: 868 | .section .rdata,"dr" 869 | .align 4 870 | LC54: 871 | .ascii "linked_list_not_to_be_confused_with_zeldad_list\0" 872 | LC55: 873 | .ascii "\11HEAD =>\0" 874 | LC56: 875 | .ascii " %d =>\0" 876 | LC57: 877 | .ascii " END\12\12\0" 878 | LC58: 879 | .ascii "\11Deleting %d...\12\12\0" 880 | .text 881 | .globl _linked_list_not_to_be_confused_with_zeldad_list 882 | .def _linked_list_not_to_be_confused_with_zeldad_list; .scl 2; .type 32; .endef 883 | _linked_list_not_to_be_confused_with_zeldad_list: 884 | LFB46: 885 | .cfi_startproc 886 | pushl %ebp 887 | .cfi_def_cfa_offset 8 888 | .cfi_offset 5, -8 889 | movl %esp, %ebp 890 | .cfi_def_cfa_register 5 891 | subl $40, %esp 892 | movl $LC54, (%esp) 893 | call _print_function_header 894 | movl $0, -16(%ebp) 895 | movl $99, 4(%esp) 896 | movl -16(%ebp), %eax 897 | movl %eax, (%esp) 898 | call _insert_at_start_of_ll 899 | movl %eax, -16(%ebp) 900 | movl $66, 4(%esp) 901 | movl -16(%ebp), %eax 902 | movl %eax, (%esp) 903 | call _insert_at_start_of_ll 904 | movl %eax, -16(%ebp) 905 | movl $33, 4(%esp) 906 | movl -16(%ebp), %eax 907 | movl %eax, (%esp) 908 | call _insert_at_start_of_ll 909 | movl %eax, -16(%ebp) 910 | movl -16(%ebp), %eax 911 | movl %eax, -12(%ebp) 912 | movl $LC55, (%esp) 913 | call _printf 914 | jmp L67 915 | L68: 916 | movl -12(%ebp), %eax 917 | movl (%eax), %eax 918 | movl %eax, 4(%esp) 919 | movl $LC56, (%esp) 920 | call _printf 921 | movl -12(%ebp), %eax 922 | movl 4(%eax), %eax 923 | movl %eax, -12(%ebp) 924 | L67: 925 | cmpl $0, -12(%ebp) 926 | jne L68 927 | movl $LC57, (%esp) 928 | call _printf 929 | movl -16(%ebp), %eax 930 | movl %eax, -12(%ebp) 931 | movl $0, -20(%ebp) 932 | jmp L69 933 | L70: 934 | movl -12(%ebp), %eax 935 | movl %eax, -20(%ebp) 936 | movl -12(%ebp), %eax 937 | movl 4(%eax), %eax 938 | movl %eax, -12(%ebp) 939 | movl -20(%ebp), %eax 940 | movl (%eax), %eax 941 | movl %eax, 4(%esp) 942 | movl $LC58, (%esp) 943 | call _printf 944 | movl -20(%ebp), %eax 945 | movl %eax, (%esp) 946 | call _free 947 | movl $0, -20(%ebp) 948 | L69: 949 | cmpl $0, -12(%ebp) 950 | jne L70 951 | movl $0, -12(%ebp) 952 | movl -12(%ebp), %eax 953 | movl %eax, -16(%ebp) 954 | nop 955 | leave 956 | .cfi_restore 5 957 | .cfi_def_cfa 4, 4 958 | ret 959 | .cfi_endproc 960 | LFE46: 961 | .def ___main; .scl 2; .type 32; .endef 962 | .section .rdata,"dr" 963 | LC59: 964 | .ascii "main\0" 965 | LC60: 966 | .ascii "quick_maths() result is %d\12\12\0" 967 | LC61: 968 | .ascii "All done!\12\12\0" 969 | .text 970 | .globl _main 971 | .def _main; .scl 2; .type 32; .endef 972 | _main: 973 | LFB47: 974 | .cfi_startproc 975 | pushl %ebp 976 | .cfi_def_cfa_offset 8 977 | .cfi_offset 5, -8 978 | movl %esp, %ebp 979 | .cfi_def_cfa_register 5 980 | andl $-16, %esp 981 | subl $112, %esp 982 | call ___main 983 | movl $LC59, (%esp) 984 | call _print_function_header 985 | movl $5, 100(%esp) 986 | movl $7, 96(%esp) 987 | movl $0, 92(%esp) 988 | movl $0, 108(%esp) 989 | jmp L72 990 | L73: 991 | movl 108(%esp), %eax 992 | leal 1(%eax), %edx 993 | movl 108(%esp), %eax 994 | movl %edx, 52(%esp,%eax,4) 995 | addl $1, 108(%esp) 996 | L72: 997 | cmpl $9, 108(%esp) 998 | jle L73 999 | movl $544024393, 25(%esp) 1000 | movl $1953701985, 29(%esp) 1001 | movl $1735289202, 33(%esp) 1002 | movl $1869357100, 37(%esp) 1003 | movl $1629514607, 41(%esp) 1004 | movl $1701650548, 45(%esp) 1005 | movl $663909, 48(%esp) 1006 | movl $0, 104(%esp) 1007 | movl $0, 104(%esp) 1008 | jmp L74 1009 | L75: 1010 | addl $1, 104(%esp) 1011 | L74: 1012 | leal 25(%esp), %edx 1013 | movl 104(%esp), %eax 1014 | addl %edx, %eax 1015 | movzbl (%eax), %eax 1016 | testb %al, %al 1017 | jne L75 1018 | movl 96(%esp), %eax 1019 | movl %eax, 4(%esp) 1020 | movl 100(%esp), %eax 1021 | movl %eax, (%esp) 1022 | call _quick_maths 1023 | movl %eax, 4(%esp) 1024 | movl $LC60, (%esp) 1025 | call _printf 1026 | call _loop_soup 1027 | call _terms_and_conditions 1028 | movl $10, 4(%esp) 1029 | leal 52(%esp), %eax 1030 | movl %eax, (%esp) 1031 | call _disarray 1032 | movl 104(%esp), %eax 1033 | movl %eax, 4(%esp) 1034 | leal 25(%esp), %eax 1035 | movl %eax, (%esp) 1036 | call _disappointers 1037 | call _struct_your_stuff 1038 | call _linked_list_not_to_be_confused_with_zeldad_list 1039 | movl $LC61, (%esp) 1040 | call _printf 1041 | call _getchar 1042 | movl $0, %eax 1043 | leave 1044 | .cfi_restore 5 1045 | .cfi_def_cfa 4, 4 1046 | ret 1047 | .cfi_endproc 1048 | LFE47: 1049 | .ident "GCC: (Rev1, Built by MSYS2 project) 12.2.0" 1050 | .def ___mingw_vfprintf; .scl 2; .type 32; .endef 1051 | .def _malloc; .scl 2; .type 32; .endef 1052 | .def _free; .scl 2; .type 32; .endef 1053 | .def _realloc; .scl 2; .type 32; .endef 1054 | .def _getchar; .scl 2; .type 32; .endef 1055 | -------------------------------------------------------------------------------- /master0Fnone_classes/1_x86_Demystified/practice/practice_compiled.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeFF0Falltrades/Tutorials/c9347157e74f914353ba793c9184780524dd67f6/master0Fnone_classes/1_x86_Demystified/practice/practice_compiled.o -------------------------------------------------------------------------------- /master0Fnone_classes/1_x86_Demystified/x86_assembly_reversing_cheat_sheet.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeFF0Falltrades/Tutorials/c9347157e74f914353ba793c9184780524dd67f6/master0Fnone_classes/1_x86_Demystified/x86_assembly_reversing_cheat_sheet.pdf -------------------------------------------------------------------------------- /master0Fnone_classes/2_Sandbox_in_a_Box/README.md: -------------------------------------------------------------------------------- 1 | # Episode 2: Sandbox in a Box 2 | 3 | ## Class Objectives 4 | 5 | - Build a simple malware analysis lab for FREE, using 2 virtual machines (Remnux and Windows 10) and several free analysis and monitoring tools 6 | - Snapshot our lab and make it exportable so we can bring it anywhere 7 | - Examine some real malware samples in our newly-built sandbox, test out the tools we installed, and discover how to pull indicators of compromise and artifacts for detections and determining what the malware is trying to accomplish 8 | - Challenge you to take what you've learned and use it to achieve an entry on the "Wall of Fame" by analyzing the included "CrackMe" program and finding all the flags! 9 | 10 | ## Class Videos 11 | 12 | - [Part 1: Building Our Malware Analysis Lab for Free](https://youtu.be/ELPWeRXxnSE) 13 | - [Part 2: Analyzing Real Malware in our New Sandbox](https://youtu.be/PffR5DCzAQI) 14 | 15 | ## Directory List & Description 16 | 17 | ``` 18 | 2_Sandbox_in_a_Box/ 19 | ├── README.md (you are here) 20 | ├── Tools_and_Resources.md (a list of tools and their download links, as well as other reading and the malware samples from the video) 21 | ├── crackme 22 | │ ├── Wall_of_Fame.md (the Wall of Fame for this course) 23 | │ └── crackme.zip (the crackme challenge program) 24 | └── parse_hashes.sh (a custom script we used in the video to parse various hashes of files) 25 | ``` 26 | 27 | ## Wall of Fame Submission Instructions 28 | 29 | 1. Download the CrackMe program ZIP (`crackme.zip`) and transfer it into your lab environment (see the video for examples of doing this, if needed), then extract `crackme.exe` 30 | 1. Use your lab tools to analyze the file as much as you can 31 | 1. Open [this Google form](https://forms.gle/nE2yFZowxhCKBPw37) to input your answers to the challenge questions and provide the name you want on the Wall of Fame 32 | 1. Check back a few minutes after your form submission, and the Wall of Fame file in the `crackme` folder should be updated with your name and the date of your achievement 33 | 1. Send me an Issue, comment, or message if you did a write-up related to this challenge, and I'll add it next to your name 34 | 1. Brag to your friends by showing them your name on the Wall of Fame and then get asked dozens of questions about what any of this means! 35 | 36 | crackme.exe SHA256 Hash: `bb203ab338be9968ba5ecbdf1b53633eb15d9be82b7bc32d4e4ade86b3467788` 37 | 38 | ## Let Me Know What You Think, and Share with Those Who Yearn to Learn! 39 | 40 | Enjoy and please leave questions and feedback on [YouTube](https://www.youtube.com/@jeff0falltrades) or [Mastodon](https://infosec.exchange/@jeFF0Falltrades)! 41 | 42 | ## Edits and Updates 43 | * Thanks to @BrakeSec for the suggestion, I added a simple helper script so you don't have to worry about commenting out the netplan configuration yourself: `toggle_netplan.sh` 44 | -------------------------------------------------------------------------------- /master0Fnone_classes/2_Sandbox_in_a_Box/Tools_and_Resources.md: -------------------------------------------------------------------------------- 1 | # Tools and Resources 2 | ## Virtualization 3 | * [Remnux Virtual Appliance](https://docs.remnux.org/install-distro/get-virtual-appliance) 4 | * [VirtualBox](https://www.virtualbox.org/wiki/Downloads) 5 | * [VirtualBox Test Builds](https://www.virtualbox.org/wiki/Testbuilds) 6 | * [Windows 10 ISO Creation Tool](https://www.microsoft.com/en-in/software-download/windows10) 7 | 8 | 9 | ## Remnux Tools List 10 | * [Bytecode-Viewer](https://github.com/Konloch/bytecode-viewer) 11 | * [RAT King Parser](https://github.com/jeFF0Falltrades/rat_king_parser) 12 | 13 | ## Windows Tools List 14 | * [7-Zip](https://www.7-zip.org/download.html) 15 | * [Capture-Py](https://github.com/fbruzzaniti/Capture-Py) 16 | * [CMD Watcher/Kahu Security Tools](https://www.kahusecurity.com/tools.html) 17 | * [de4dot-cex](https://github.com/ViRb3/de4dot-cex) 18 | * [Detect-It-Easy](https://github.com/horsicq/Detect-It-Easy) 19 | * [disable-defender.ps1](https://github.com/jeremybeaume/tools/blob/master/disable-defender.ps1) 20 | * [Disable Windows Defender](https://www.windowscentral.com/how-permanently-disable-windows-defender-windows-10) 21 | * [Disable Windows Updates](https://www.windowscentral.com/how-stop-updates-installing-automatically-windows-10) 22 | * [dnSpy](https://github.com/dnSpy/dnSpy) 23 | * [Ghidra](https://github.com/NationalSecurityAgency/ghidra/releases) 24 | * [Ghidra Installation Guide](https://htmlpreview.github.io/?https://github.com/NationalSecurityAgency/ghidra/blob/Ghidra_11.1.1_build/GhidraDocs/InstallationGuide.html#Requirements) 25 | * [Google Chrome Standalone Installer](https://www.google.com/intl/en/chrome/?standalone=1) 26 | * [hostname-changer](https://github.com/d4rksystem/hostname-changer) 27 | * [IDA Free](https://hex-rays.com/ida-free/) 28 | * [JDK 17 Download](https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html) 29 | * [LibreOffice](https://www.libreoffice.org/download/download-libreoffice/) 30 | * [.NET Framework 8.0](https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/sdk-8.0.302-windows-x64-installer) 31 | * [pafish](https://github.com/a0rtega/pafish) 32 | * [pe-sieve](https://github.com/hasherezade/pe-sieve) 33 | * [Process Monitor](https://learn.microsoft.com/en-us/sysinternals/downloads/procmon) 34 | * [ProcessSpawnControl](https://github.com/felixweyne/ProcessSpawnControl) 35 | * [Python](https://www.python.org/downloads/) 36 | * [System Informer](https://systeminformer.sourceforge.io/) 37 | * [VBoxCloak](https://github.com/d4rksystem/VBoxCloak) 38 | * [VbsEdit](https://vbsedit.adersoft.com/vo.asp) 39 | * [Wireshark](https://www.wireshark.org/download.html) 40 | * [x64dbg](https://x64dbg.com/) 41 | 42 | ## Additional Resources 43 | * [antivmdetection](https://github.com/nsmfoo/antivmdetection) 44 | * ["Evasive Malware" by Kyle Cucci](https://nostarch.com/evasive-malware) 45 | * [FLARE-VM](https://github.com/mandiant/flare-vm) 46 | * [Hasherezade Projects](https://hasherezade.github.io/) 47 | * [IP Subnet Calculator](https://jodies.de/ipcalc) 48 | * [List of MAC Addresses](https://gist.github.com/aallan/b4bb86db86079509e6159810ae9bd3e4) 49 | * [MalwareBazaar](https://bazaar.abuse.ch/) 50 | 51 | 52 | ## Malware Samples Featured in Video 53 | * [Adwind](https://bazaar.abuse.ch/sample/c28091facc92091cf730431a4477ae321f3c6da10a6109ab1d528889a5e01ca0) 54 | * [AsyncRAT](https://bazaar.abuse.ch/sample/a0bf6f09f394949c603b878ab42b001155d347e5812b59f09a2ab9d387d548f6) 55 | * [Royal Ransomware](https://bazaar.abuse.ch/sample/beef7e428f26c583dd92962cbe886f2e4286825a1637b7a427ce84139ab6307a) 56 | * [XWorm](https://bazaar.abuse.ch/sample/e4c179fa5bc03b07e64e65087afcbad04d40475204ebb0a0bc7d77f071222656) 57 | -------------------------------------------------------------------------------- /master0Fnone_classes/2_Sandbox_in_a_Box/crackme/Wall_of_Fame.md: -------------------------------------------------------------------------------- 1 | # Wall of Fame: Sandbox in a Box 2 | 3 | ## Congratulations to these incredible viewers who successfully found all the flags! 4 | 5 | ``` 6 | jeFF0Falltrades - 09-JUL-2024 7 | lukef - 11-JUL-2024 8 | ihacksi - 31-JUL-2024 (Write-Up: https://medium.com/@huseyin.eksi/malware-analysis-of-crackme-challenge-77170cc4b3cds) 9 | Venkata Raju Putra - mrpvr - 03-AUG-2024 10 | garr3ttmj - 04-AUG-2024 (Write-Up: https://github.com/garr3ttmjo/Writeups/blob/main/Sandbox%20in%20a%20Box/README.md) 11 | Moshe - 12-AUG-2024 12 | Paliak - 27-OCT-2024 13 | K0enM - 21-NOV-2024 (Write-Up: https://www.koenmolenaar.nl/write-ups/jeff0falltrades-sandbox-crackme/) 14 | scooper - 12-FEB-2025 15 | ``` 16 | -------------------------------------------------------------------------------- /master0Fnone_classes/2_Sandbox_in_a_Box/crackme/crackme.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeFF0Falltrades/Tutorials/c9347157e74f914353ba793c9184780524dd67f6/master0Fnone_classes/2_Sandbox_in_a_Box/crackme/crackme.zip -------------------------------------------------------------------------------- /master0Fnone_classes/2_Sandbox_in_a_Box/parse_hashes.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Author: jeFF0Falltrades 4 | # 5 | # parse_hashes.sh extracts the following hashes for one or more files: 6 | # - MD5 7 | # - SHA1 8 | # - SHA256 9 | # - SSDEEP/Fuzzy Hash (requires ssdeep to be installed) 10 | # - Import Hash (requires python and the pefile module to be installed) 11 | # 12 | # MIT License 13 | # 14 | # Copyright (c) 2024 Jeff Archer 15 | # 16 | # Permission is hereby granted, free of charge, to any person obtaining a copy 17 | # of this software and associated documentation files (the "Software"), to deal 18 | # in the Software without restriction, including without limitation the rights 19 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 20 | # copies of the Software, and to permit persons to whom the Software is 21 | # furnished to do so, subject to the following conditions: 22 | # 23 | # The above copyright notice and this permission notice shall be included in all 24 | # copies or substantial portions of the Software. 25 | # 26 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 27 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 28 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 29 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 30 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 31 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 32 | # SOFTWARE. 33 | imphash_script=" 34 | try: 35 | from pefile import PE 36 | file = PE('%s') 37 | print(file.get_imphash()) 38 | except: 39 | raise SystemExit(0) 40 | " 41 | 42 | # Determine which version of Python to run 43 | command -v python3 >/dev/null && PYTHON_BIN=python3 || PYTHON_BIN=python 44 | 45 | for filename in $@; do 46 | basename "$filename" 47 | md5sum "$filename" | awk ' { print $1 }' 48 | sha1sum "$filename" | awk ' { print $1 }' 49 | sha256sum "$filename" | awk ' { print $1 }' 50 | ssdeep "$filename" | grep -Po "([0-9]+:.*(?=,))" 51 | printf "$imphash_script" "$filename" | $PYTHON_BIN 52 | done 53 | -------------------------------------------------------------------------------- /master0Fnone_classes/2_Sandbox_in_a_Box/toggle_netplan.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Author: jeFF0Falltrades 4 | # 5 | # toggle_netplan.sh automates the process of swapping the netplan configuration 6 | # of our "Sandbox in a Box" Remnux machine so that you can toggle between the 7 | # configuration used to run upgrades/updates, and the configuration used to 8 | # prepare the machine for use in the internal (virtual) network. 9 | # 10 | # REMEMBER: To run this as root, and switch your VM network afterwards as well 11 | # 12 | # MIT License 13 | # 14 | # Copyright (c) 2024 Jeff Archer 15 | # 16 | # Permission is hereby granted, free of charge, to any person obtaining a copy 17 | # of this software and associated documentation files (the "Software"), to deal 18 | # in the Software without restriction, including without limitation the rights 19 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 20 | # copies of the Software, and to permit persons to whom the Software is 21 | # furnished to do so, subject to the following conditions: 22 | # 23 | # The above copyright notice and this permission notice shall be included in all 24 | # copies or substantial portions of the Software. 25 | # 26 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 27 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 28 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 29 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 30 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 31 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 32 | # SOFTWARE. 33 | if [ "$EUID" -ne 0 ]; then 34 | echo "This script must be run as root." 35 | exit 1 36 | fi 37 | 38 | NETPLAN_CONFIG="/etc/netplan/01-netcfg.yaml" 39 | 40 | enable_internet=$(cat <<'EOF' 41 | # This file describes the network interfaces available on your system 42 | # For more information, see netplan(5). 43 | network: 44 | version: 2 45 | renderer: networkd 46 | ethernets: 47 | enp0s17: 48 | dhcp4: yes 49 | nameservers: 50 | addresses: [8.8.8.8] 51 | EOF 52 | ) 53 | 54 | disable_internet=$(cat <<'EOF' 55 | # This file describes the network interfaces available on your system 56 | # For more information, see netplan(5). 57 | network: 58 | version: 2 59 | renderer: networkd 60 | ethernets: 61 | enp0s17: 62 | dhcp4: no 63 | addresses: [10.10.10.3/29] 64 | gateway4: 10.10.10.3 65 | EOF 66 | ) 67 | 68 | if grep -q "dhcp4: yes" "$NETPLAN_CONFIG"; then 69 | echo "$disable_internet" > "$NETPLAN_CONFIG" 70 | msg="Swapped to internal network configuration" 71 | else 72 | echo "$enable_internet" > "$NETPLAN_CONFIG" 73 | msg="Swapped to NAT configuration" 74 | fi 75 | 76 | netplan apply 77 | echo $msg 78 | -------------------------------------------------------------------------------- /master0Fnone_classes/README.md: -------------------------------------------------------------------------------- 1 | # master0Fnone Classes 2 | 3 | The jeFF0Falltrades master0Fnone Class series is a collection of free, long-form online courses made to make learning topics - like reverse engineering - more accessible (and fun) to everyone. 4 | 5 | ### Episode List 6 | 7 | 1. [x86 Assembly Demystified](https://github.com/jeFF0Falltrades/Tutorials/tree/master/master0Fnone_classes/1_x86_Demystified/): Basic overview of the Language Processing System, x86 assembly language, Ghidra, x64dbg, and a CrackMe challenge (with Wall of Fame) 8 | 2. [Sandbox in a Box](https://github.com/jeFF0Falltrades/Tutorials/tree/master/master0Fnone_classes/2_Sandbox_in_a_Box/): Building a malware analysis lab from scratch using only free software and using this lab to analyze real malware (CrackMe + Wall of Fame included) 9 | -------------------------------------------------------------------------------- /rct_full_res/README.md: -------------------------------------------------------------------------------- 1 | # Roller Coaster Tycoon (1999) Full Resolution Game Patch (with Video Tutorial) 2 | Patches the original 1999 Roller Coaster Tycoon game executable (including expansions Corkscrew Follies and/or Loopy Landscapes) for full resolution on modern systems. 3 | 4 | This patch can be used to allow windowed mode to maximize the window to the specified resolution, or (by default) patch the game to run in full screen mode at the specified resolution. 5 | 6 | It will create a copy of the Roller Coaster Tycoon executable file, and it will not modify the original executable, so you needn't worry about breaking your base installation. 7 | 8 | # Video Tutorial 9 | For a full walkthrough of how this patch was created, and a general introduction to reverse engineering and game patching, see the accompanying video: 10 | 11 | [Reverse Engineering/Game Patching Tutorial: Full Res Roller Coaster Tycoon with Ghidra+x64dbg+Python](https://youtu.be/cwBoUuy4nGc) 12 | 13 | # Installation 14 | ## Python 15 | * Download [Python](https://www.python.org/downloads/) (Python 3+ preferred) 16 | * Follow the usage instructions below to run the patch program 17 | ## EXE 18 | * Download `rct_patch.exe` 19 | * Run the executable with the same options shown below, e.g. 20 | 21 | ``` 22 | $ ./rct_patch.exe auto -w -t ~/Downloads/RCT/RCT.EXE 1920 1080 -o ~/Downloads/RCT/rct_patched.exe 23 | ``` 24 | 25 | # Usage 26 | ## Checking an Executable for Compatibility 27 | ``` 28 | $ python rct_patch.py check -h 29 | usage: rct_patch.py check [-h] [-t TARGET] 30 | 31 | optional arguments: 32 | -h, --help show this help message and exit 33 | -t TARGET, --target TARGET 34 | Full path to RCT.EXE (defaults to local directory) 35 | ``` 36 | 37 | The `check` command can be used to first test if an executable can be patched using this script. 38 | 39 | It performs a (lazy) check to ensure the byte sequences to be patched are present in the file. 40 | 41 | The target file should be your primary Roller Coaster Tycoon executable (RCT.exe). 42 | 43 | 44 | **Successful Check** 45 | ``` 46 | $ python rct_patch.py check -t ~/Downloads/RCT/RCT.EXE 47 | Success! 48 | ``` 49 | 50 | **Unsuccessful Check** 51 | ``` 52 | $ python rct_patch.py check -t ~/Downloads/not_rct.exe 53 | File failed offset check: ~/not_rct.exe. Use manual mode for replacements or modify patching script. 54 | ``` 55 | 56 | ## Using Manual Mode to Retrieve Replacement Strings 57 | ``` 58 | $ python rct_patch.py manual -h 59 | usage: rct_patch.py manual [-h] width height 60 | 61 | positional arguments: 62 | width Your desired resolution width 63 | height Your desired resolution height 64 | 65 | optional arguments: 66 | -h, --help show this help message and exit 67 | ``` 68 | 69 | You can use the `manual` command to specify a desired width and height, and the patch will show the strings to find and replace in the Roller Coaster Tycoon file (using a hex editor), along with which strings to replace them with: 70 | 71 | ``` 72 | $ python rct_patch.py manual 1920 1080 73 | 74 | Search String 75 | --> Replacement 76 | 77 | 78 | E8 86 7A FF FF 79 | --> E8 33 7A FF FF 80 | 81 | 00 05 00 00 0F 8E 07 00 00 00 C7 45 FC 00 05 00 00 81 7D F4 00 04 00 00 0F 8E 07 00 00 00 C7 45 F4 00 04 00 00 82 | --> 80 07 00 00 0F 8E 07 00 00 00 C7 45 FC 80 07 00 00 81 7D F4 38 04 00 00 0F 8E 07 00 00 00 C7 45 F4 38 04 00 00 83 | 84 | Success! 85 | ``` 86 | 87 | Note that for "Windowed Mode", only the second string (`00 05...`) needs to be replaced. 88 | 89 | # Using Automatic Mode to Patch Easily 90 | ``` 91 | $ python rct_patch.py auto -h 92 | usage: rct_patch.py auto [-h] [-t TARGET] [-o OUTFILE] [-w] width height 93 | 94 | positional arguments: 95 | width Your desired resolution width 96 | height Your desired resolution height 97 | 98 | optional arguments: 99 | -h, --help show this help message and exit 100 | -t TARGET, --target TARGET 101 | Full path to RCT.EXE (defaults to local directory) 102 | -o OUTFILE, --outfile OUTFILE 103 | Desired output file name (defaults to `rct_patched.exe`) 104 | -w, --windowed Patch for windowed mode only 105 | ``` 106 | 107 | The `auto` command be used to patch the primary Roller Coaster Tycoon executable automatically for either full screen play or windowed play (where windowed mode is patched to maximize the window to the specified resolution): 108 | 109 | **Patching for Full Screen Mode** 110 | ``` 111 | $ python rct_patch.py auto -t ~/Downloads/RCT/RCT.EXE 1920 1080 -o ~/Downloads/RCT/rct_patched.exe 112 | 113 | ``` 114 | 115 | **Patching for Windowed Mode** 116 | ``` 117 | $ python rct_patch.py auto -w -t ~/Downloads/RCT/RCT.EXE 1920 1080 -o ~/Downloads/RCT/rct_patched.exe 118 | ``` 119 | -------------------------------------------------------------------------------- /rct_full_res/rct_patch.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeFF0Falltrades/Tutorials/c9347157e74f914353ba793c9184780524dd67f6/rct_full_res/rct_patch.exe -------------------------------------------------------------------------------- /rct_full_res/rct_patch.py: -------------------------------------------------------------------------------- 1 | # rct_patch.py 2 | # 3 | # Author: jeFF0Falltrades 4 | # 5 | # A patching script for the Roller Coaster Tycoon (1999) game 6 | # executable for play on modern systems at full resolution. 7 | # 8 | # Homepage with Video Tutorial: 9 | # https://github.com/jeFF0Falltrades/Game-Patches/tree/master/rct_full_res 10 | # 11 | # MIT License 12 | # 13 | # Copyright (c) 2020 Jeff Archer 14 | # 15 | # Permission is hereby granted, free of charge, to any person obtaining a copy 16 | # of this software and associated documentation files (the "Software"), to deal 17 | # in the Software without restriction, including without limitation the rights 18 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | # copies of the Software, and to permit persons to whom the Software is 20 | # furnished to do so, subject to the following conditions: 21 | # 22 | # The above copyright notice and this permission notice shall be included in all 23 | # copies or substantial portions of the Software. 24 | # 25 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 31 | # SOFTWARE. 32 | 33 | from argparse import ArgumentParser, RawTextHelpFormatter 34 | from os.path import isfile 35 | 36 | # Dict of both hardcoded and variable values to be checked/patched 37 | PATCHES = { 38 | 'FULL_SCREEN': { 39 | # Patches default window function to use full screen mode 40 | 'E8 86 7A FF FF': 'E8 33 7A FF FF' 41 | }, 42 | 'WINDOWED': { 43 | # Patches maximum allowable resolution for windowed mode 44 | '00 05 00 00 0F 8E 07 00 00 00 C7 45 FC 00 05 00 00 81 7D F4 00 04 00 00 0F 8E 07 00 00 00 C7 45 F4 00 04 00 00': 45 | '{wl} {wh} 00 00 0F 8E 07 00 00 00 C7 45 FC {wl} {wh} 00 00 81 7D F4 {hl} {hh} 00 00 0F 8E 07 00 00 00 C7 45 F4 {hl} {hh} 00 00' 46 | } 47 | } 48 | 49 | 50 | # Gets command line arguments 51 | def getCLAs(): 52 | ap = ArgumentParser( 53 | description= 54 | 'Roller Coaster Tycoon (1999) Full Resolution Patch by jeFF0Falltrades\n\nHomepage: https://github.com/jeFF0Falltrades/Game-Patches/tree/master/rct_full_res', 55 | formatter_class=RawTextHelpFormatter) 56 | sp = ap.add_subparsers(dest='cmd') 57 | auto = sp.add_parser( 58 | 'auto', 59 | help= 60 | 'Attempt to patch the program automatically (Patches for full screen mode by default)' 61 | ) 62 | auto.add_argument('width', help='Your desired resolution width') 63 | auto.add_argument('height', help='Your desired resolution height') 64 | auto.add_argument( 65 | '-t', 66 | '--target', 67 | default='RCT.exe', 68 | help='Full path to RCT.EXE (defaults to local directory)') 69 | auto.add_argument( 70 | '-o', 71 | '--outfile', 72 | default='rct_patched.exe', 73 | help='Desired output file name (defaults to `rct_patched.exe`)') 74 | auto.add_argument('-w', 75 | '--windowed', 76 | action='store_true', 77 | help='Patch for windowed mode only') 78 | check = sp.add_parser( 79 | 'check', help='Check a file for compatibility with auto-patching mode') 80 | check.add_argument( 81 | '-t', 82 | '--target', 83 | default='RCT.exe', 84 | help='Full path to RCT.EXE (defaults to local directory)') 85 | man = sp.add_parser( 86 | 'manual', 87 | help= 88 | 'Do not patch the file, just show the necessary hex replacements for manual search/replace with a hex editor' 89 | ) 90 | man.add_argument('width', help='Your desired resolution width') 91 | man.add_argument('height', help='Your desired resolution height') 92 | return ap.parse_args() 93 | 94 | 95 | # Populates empty dictionary values based on user input 96 | def populateVals(w, h): 97 | try: 98 | w = int(w) 99 | h = int(h) 100 | except ValueError: 101 | raise SystemExit( 102 | 'Invalid width and height values received: {}x{}'.format( 103 | args.width, args.height)) 104 | 105 | for key in PATCHES['WINDOWED']: 106 | PATCHES['WINDOWED'][key] = PATCHES['WINDOWED'][key].format( 107 | wl=hex(w & 0XFF).replace('0x', '').zfill(2), 108 | wh=hex((w & 0XFF00) >> 8).replace('0x', '').zfill(2), 109 | hl=hex(h & 0XFF).replace('0x', '').zfill(2), 110 | hh=hex((h & 0XFF00) >> 8).replace('0x', '').zfill(2)) 111 | 112 | 113 | # Checks if default values are found in target file 114 | def fileCheck(fp): 115 | data = '' 116 | with open(fp, 'rb') as f: 117 | data = f.read() 118 | for key in PATCHES: 119 | for def_val in PATCHES[key]: 120 | if data.find(bytearray.fromhex(def_val)) == -1: 121 | return False 122 | return True 123 | 124 | 125 | # Prints hex string replacements for manual patching 126 | def printReplacements(): 127 | print('\n{}\n\t--> {}\n\n'.format('Search String', 'Replacement')) 128 | for key in PATCHES: 129 | for k, v in PATCHES[key].items(): 130 | print('{}\n\t--> {}\n'.format(k, v)) 131 | 132 | 133 | # Patches for full screen mode 134 | def patchFullScreen(fp, outfile): 135 | data = '' 136 | with open(fp, 'rb') as f: 137 | data = f.read() 138 | for key in PATCHES: 139 | for k, v in PATCHES[key].items(): 140 | data = data.replace(bytearray.fromhex(k), bytearray.fromhex(v)) 141 | with open(outfile, 'wb') as o: 142 | o.write(data) 143 | 144 | 145 | # Patches for windowed mode 146 | def patchWindowed(fp, outfile): 147 | data = '' 148 | with open(fp, 'rb') as f: 149 | data = f.read() 150 | for k, v in PATCHES['WINDOWED'].items(): 151 | data = data.replace(bytearray.fromhex(k), bytearray.fromhex(v)) 152 | with open(outfile, 'wb') as o: 153 | o.write(data) 154 | 155 | 156 | # Checks if file exists and passes predefined checks 157 | def doFileChecks(fp): 158 | if not isfile(fp): 159 | raise SystemExit( 160 | 'Cannot find file {}. Check file path and try again'.format(fp)) 161 | if not fileCheck(fp): 162 | raise SystemExit( 163 | 'File failed offset check: {}. Use manual mode for replacements or modify patching script.' 164 | .format(fp)) 165 | 166 | 167 | if __name__ == '__main__': 168 | args = getCLAs() 169 | 170 | if args.cmd == 'check': 171 | doFileChecks(args.target) 172 | 173 | elif args.cmd == 'manual': 174 | populateVals(args.width, args.height) 175 | printReplacements() 176 | 177 | elif args.cmd == 'auto': 178 | populateVals(args.width, args.height) 179 | doFileChecks(args.target) 180 | if args.windowed: 181 | patchWindowed(args.target, args.outfile) 182 | else: 183 | patchFullScreen(args.target, args.outfile) 184 | 185 | else: 186 | raise SystemExit( 187 | 'Unknown command received. Use `python rct_patch.py -h` for help') 188 | 189 | print('Success!') -------------------------------------------------------------------------------- /rct_horror_mod/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ -------------------------------------------------------------------------------- /rct_horror_mod/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Jeff Archer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /rct_horror_mod/README.md: -------------------------------------------------------------------------------- 1 | # RollerCoaster Tycoon Horror Mod (with Video Tutorial) 2 | 3 | Contains scripts to: 4 | 5 | 1. Display or dump color palette and Device-Independent Bitmap (DIB) information 6 | 2. Dump all bitmap resources 7 | 3. Patch the game so that any explosion results in park visitors clapping, along with replacing the explosion animation with an animation of choice, while playing a WAV file of choice (by default choosing a spooky skull animation and a horror track included with the game), and setting the game window title to "RollerCoaster Diecoon" for extra flair 8 | 9 | using the original 1999 Roller Coaster Tycoon Gold Edition game executable. 10 | 11 | Enjoy and please leave questions and feedback on [YouTube](https://www.youtube.com/@jeff0falltrades) or [Mastodon](https://infosec.exchange/@jeFF0Falltrades)! 12 | 13 | # Video Tutorial 14 | 15 | For a full walkthrough of how this mod was created, and a general introduction to reverse engineering and game patching, see the accompanying videos (specifically Part 2 for the code overview): 16 | 17 | Part 1: [Modding RollerCoaster Tycoon into a Peele Horror Film](https://www.youtube.com/watch?v=1MOrjGZ4hbo) 18 | 19 | Part 2: [[Game Mod Code Deep Dive] Modding RollerCoaster Tycoon into a Peele Horror Film](https://youtu.be/PYOUgevLRfc) 20 | 21 | # Installation 22 | 23 | ## Python 24 | 25 | - Clone this repo 26 | - Install requirements: `python -m pip install -r requirements.txt` 27 | - See below for usage notes 28 | 29 | # Usage 30 | 31 | ## General Usage 32 | 33 | ``` 34 | usage: rct_horror_mod.py [-h] {bitmaps,dump,show,patch} ... 35 | 36 | positional arguments: 37 | {bitmaps,dump,show,patch} 38 | bitmaps Given a palette file, CSG1.dat file, and CSG1i.dat file, parse bitmaps from CSG1.dat and save them to disk 39 | dump Dump palette and DIB data from an active RCT process 40 | show Display, but don't save, palette and DIB data from an active RCT process 41 | patch Patch the RCT executable to enable "horror mode" 42 | 43 | options: 44 | -h, --help show this help message and exit 45 | ``` 46 | 47 | ## Bitmaps Command 48 | 49 | ``` 50 | usage: rct_horror_mod.py bitmaps [-h] path_palette path_csg1 path_csg1i path_outdir 51 | 52 | positional arguments: 53 | path_palette Path to palette file 54 | path_csg1 Path to CSG1.dat file 55 | path_csg1i Path to CSG1i.dat file 56 | path_outdir Directory to output extracted bitmaps to 57 | 58 | options: 59 | -h, --help show this help message and exit 60 | ``` 61 | 62 | ### Example 63 | 64 | ``` 65 | $ python rct_horror_mod.py bitmaps palette.bmp CSG1.DAT csg1i.dat bitmaps/ 66 | INFO:root:Parsing palette from palette.bmp... 67 | INFO:root:Parsing TGraphicRecords from csg1i.dat... 68 | INFO:root:Extracting bitmaps from CSG1.DAT... 69 | INFO:root:Successfully extracted 69917 bitmaps to bitmaps/ 70 | ``` 71 | 72 | ## Dump Command 73 | 74 | ``` 75 | usage: rct_horror_mod.py dump [-h] path_palette path_dib 76 | 77 | positional arguments: 78 | path_palette Path to save palette file 79 | path_dib Path to save DIB file 80 | 81 | options: 82 | -h, --help show this help message and exit 83 | ``` 84 | 85 | ### Example 86 | 87 | ``` 88 | $ python rct_horror_mod.py dump palette.bmp dib.bmp 89 | INFO:root:DIB Located at: 0xcac0000 90 | 91 | INFO:root:BITMAPINFO located at: 0x2b01b98 92 | 93 | INFO:root:Parsed the following BITMAPINFOHEADER: 94 | {'biBitCount': 8, 95 | 'biClrImportant': 256, 96 | 'biClrUsed': 256, 97 | 'biCompression': 0, 98 | 'biHeight': -1024, 99 | 'biPlanes': 1, 100 | 'biSize': 40, 101 | 'biSizeImage': 1310720, 102 | 'biWidth': 1280, 103 | 'biXPelsPerMeter': 0, 104 | 'biYPelsPerMeter': 0} 105 | 106 | INFO:root:Color Table located at: 0x2b01bc0 107 | 108 | INFO:root:Resolution: 640 x 480 109 | 110 | INFO:root:Generating palette... 111 | 112 | INFO:root:Generating DIB... 113 | ``` 114 | 115 | ## Show Command 116 | 117 | ``` 118 | usage: rct_horror_mod.py show [-h] 119 | 120 | options: 121 | -h, --help show this help message and exit 122 | ``` 123 | 124 | ### Example 125 | 126 | ``` 127 | $ python rct_horror_mod.py show 128 | INFO:root:DIB Located at: 0xcac0000 129 | 130 | INFO:root:BITMAPINFO located at: 0x2b01b98 131 | 132 | INFO:root:Parsed the following BITMAPINFOHEADER: 133 | {'biBitCount': 8, 134 | 'biClrImportant': 256, 135 | 'biClrUsed': 256, 136 | 'biCompression': 0, 137 | 'biHeight': -1024, 138 | 'biPlanes': 1, 139 | 'biSize': 40, 140 | 'biSizeImage': 1310720, 141 | 'biWidth': 1280, 142 | 'biXPelsPerMeter': 0, 143 | 'biYPelsPerMeter': 0} 144 | 145 | INFO:root:Color Table located at: 0x2b01bc0 146 | 147 | INFO:root:Resolution: 640 x 480 148 | 149 | INFO:root:Generating palette... 150 | 151 | INFO:root:Generating DIB... 152 | ``` 153 | 154 | ## Patch Command 155 | 156 | ``` 157 | usage: rct_horror_mod.py patch [-h] [-i INFILE] [-o OUTFILE] [-b SELECTED_BITMAP_OFFSET] [-t SELECTED_TRACK] 158 | 159 | options: 160 | -h, --help show this help message and exit 161 | -i INFILE, --infile INFILE 162 | Path to the RCT executable to modify 163 | -o OUTFILE, --outfile OUTFILE 164 | Path to the patched output executable (NOTE: The default value is appended with '_PATCHED' to avoid accidental overwriting of RCT.exe in the 165 | same directory; You should specify a different path or rename the patched executable to 'RCT.exe' to avoid conflicts in running the game) 166 | -b SELECTED_BITMAP_OFFSET, --selected_bitmap_offset SELECTED_BITMAP_OFFSET 167 | Hex offset to the bitmap/animation to play (default is "Skelly" @ b94a0) instead of an explosion 168 | -t SELECTED_TRACK, --selected_track SELECTED_TRACK 169 | Path to the DAT/WAV track to play upon modification (default is CSS25.DAT at its default Windows location) 170 | ``` 171 | 172 | ### Example 173 | 174 | ``` 175 | $ python rct_horror_mod.py patch -i RCT.EXE -o patch/RCT.EXE -b b94a0 -t "C:\Program Files (x86)\Hasbro Interactive\RollerCoaster Tycoon\Data\CSS25.DAT" 176 | INFO:root:Attempting to patch program... 177 | INFO:root:Successfully replaced explosion bitmap with bitmap at offset 0xb94a0 178 | INFO:root:Successfully set audio track to C:\Program Files (x86)\Hasbro Interactive\RollerCoaster Tycoon\Data\CSS25.DAT 179 | INFO:root:Shellcode offset 4984514 translated to Virtual Address 0xc412c2 180 | INFO:root:Successfully wrote shellcode to 0xc412c2 181 | INFO:root:Successfully patched call instruction for shellcode 182 | INFO:root:Wrote output file to patch/RCT.EXE 183 | INFO:root:Patch successful! 184 | ``` 185 | -------------------------------------------------------------------------------- /rct_horror_mod/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeFF0Falltrades/Tutorials/c9347157e74f914353ba793c9184780524dd67f6/rct_horror_mod/__init__.py -------------------------------------------------------------------------------- /rct_horror_mod/rct_horror_mod.py: -------------------------------------------------------------------------------- 1 | # Author: jeFF0Falltrades 2 | 3 | # From the video "Modding RollerCoaster Tycoon into a Peele Horror Film": 4 | 5 | # GitHub: https://github.com/jeFF0Falltrades/Tutorials/tree/master/rct_horror_mod 6 | 7 | # YouTube: https://www.youtube.com/watch?v=1MOrjGZ4hbo 8 | 9 | # MIT License 10 | 11 | # Copyright (c) 2023 Jeff Archer 12 | 13 | # Permission is hereby granted, free of charge, to any person obtaining a copy 14 | # of this software and associated documentation files (the "Software"), to deal 15 | # in the Software without restriction, including without limitation the rights 16 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | # copies of the Software, and to permit persons to whom the Software is 18 | # furnished to do so, subject to the following conditions: 19 | 20 | # The above copyright notice and this permission notice shall be included in all 21 | # copies or substantial portions of the Software. 22 | 23 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | # SOFTWARE. 30 | import logging 31 | 32 | from argparse import ArgumentParser 33 | from utils.bitmap_extract import bitmap_extract 34 | from utils.dib_palette_dump import dib_palette_dump 35 | from utils.rct_horror_patcher import patch 36 | 37 | 38 | def parse_args(): 39 | parser = ArgumentParser() 40 | subparsers = parser.add_subparsers(dest="command", required=True) 41 | 42 | # Subparser with arguments for 'bitmaps' command 43 | arg_bitmaps = subparsers.add_parser( 44 | "bitmaps", 45 | help=( 46 | "Given a palette file, CSG1.dat file, and CSG1i.dat file, parse" 47 | " bitmaps from CSG1.dat and save them to disk" 48 | ), 49 | ) 50 | arg_bitmaps.add_argument("path_palette", help="Path to palette file") 51 | arg_bitmaps.add_argument("path_csg1", help="Path to CSG1.dat file") 52 | arg_bitmaps.add_argument("path_csg1i", help="Path to CSG1i.dat file") 53 | arg_bitmaps.add_argument( 54 | "path_outdir", help="Directory to output extracted bitmaps to" 55 | ) 56 | 57 | # Subparser with arguments for 'dump' command 58 | arg_dump = subparsers.add_parser( 59 | "dump", 60 | help=( 61 | "Dump palette and DIB data from an active RCT process (WARNING:" 62 | " Must be run in Windowed mode)" 63 | ), 64 | ) 65 | arg_dump.add_argument("path_palette", help="Path to save palette file") 66 | arg_dump.add_argument("path_dib", help="Path to save DIB file") 67 | 68 | # Subparser with arguments for 'patch' command 69 | arg_patch = subparsers.add_parser( 70 | "patch", help='Patch the RCT executable to enable "horror mode"' 71 | ) 72 | arg_patch.add_argument( 73 | "-i", 74 | "--infile", 75 | default="RCT.EXE", 76 | help="Path to the RCT executable to modify", 77 | ) 78 | arg_patch.add_argument( 79 | "-o", 80 | "--outfile", 81 | default="RCT_PATCHED.EXE", 82 | help=( 83 | "Path to the patched output executable (NOTE: The default value is" 84 | " appended with '_PATCHED' to avoid accidental overwriting of" 85 | " RCT.exe in the same directory; You should specify a different" 86 | " path or rename the patched executable to 'RCT.exe' to avoid" 87 | " conflicts in running the game)" 88 | ), 89 | ) 90 | # Defaults for these two args are set to the same used in the tutorial video 91 | arg_patch.add_argument( 92 | "-b", 93 | "--selected_bitmap_offset", 94 | default="b94a0", 95 | help=( 96 | 'Hex offset to the bitmap/animation to play (default is "Skelly" @' 97 | " b94a0) instead of an explosion" 98 | ), 99 | ) 100 | arg_patch.add_argument( 101 | "-t", 102 | "--selected_track", 103 | default=( 104 | "C:\Program Files (x86)\Hasbro Interactive\RollerCoaster" 105 | " Tycoon\Data\CSS25.DAT" 106 | ), 107 | help=( 108 | "Path to the DAT/WAV track to play upon modification (default is" 109 | " CSS25.DAT at its default Windows location)" 110 | ), 111 | ) 112 | 113 | # Subparser for 'show' command (requires no arguments) 114 | subparsers.add_parser( 115 | "show", 116 | help=( 117 | "Display, but don't save, palette and DIB data from an active RCT" 118 | " process (WARNING: Must be run in Windowed mode)" 119 | ), 120 | ) 121 | 122 | return parser.parse_args() 123 | 124 | 125 | # Since all of the calling functions share a similar format, use a Python 126 | # dict to send the args to the correct function based on the command used 127 | FUNC_DISPATCH = { 128 | "bitmaps": bitmap_extract, 129 | "dump": dib_palette_dump, 130 | "patch": patch, 131 | "show": dib_palette_dump, 132 | } 133 | 134 | if __name__ == "__main__": 135 | logging.basicConfig(level=logging.INFO) 136 | try: 137 | args = parse_args() 138 | FUNC_DISPATCH[args.command](args) 139 | except Exception as e: 140 | logging.exception(e) 141 | -------------------------------------------------------------------------------- /rct_horror_mod/requirements.txt: -------------------------------------------------------------------------------- 1 | pefile 2 | pillow 3 | psutil 4 | -------------------------------------------------------------------------------- /rct_horror_mod/structs/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeFF0Falltrades/Tutorials/c9347157e74f914353ba793c9184780524dd67f6/rct_horror_mod/structs/__init__.py -------------------------------------------------------------------------------- /rct_horror_mod/structs/bitmap.py: -------------------------------------------------------------------------------- 1 | # Author: jeFF0Falltrades 2 | 3 | # From the video "Modding RollerCoaster Tycoon into a Peele Horror Film": 4 | 5 | # GitHub: https://github.com/jeFF0Falltrades/Tutorials/tree/master/rct_horror_mod 6 | 7 | # YouTube: https://www.youtube.com/watch?v=1MOrjGZ4hbo 8 | 9 | # MIT License 10 | 11 | # Copyright (c) 2023 Jeff Archer 12 | 13 | # Permission is hereby granted, free of charge, to any person obtaining a copy 14 | # of this software and associated documentation files (the "Software"), to deal 15 | # in the Software without restriction, including without limitation the rights 16 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | # copies of the Software, and to permit persons to whom the Software is 18 | # furnished to do so, subject to the following conditions: 19 | 20 | # The above copyright notice and this permission notice shall be included in all 21 | # copies or substantial portions of the Software. 22 | 23 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | # SOFTWARE. 30 | from dataclasses import dataclass 31 | from struct import iter_unpack 32 | from structs.rgbquad import RGBQUAD 33 | from typing import List, Tuple 34 | 35 | 36 | # The structure of bitmaps used by RCT was sourced from: 37 | # https://tid.rctspace.com/csg/csg.html 38 | # 39 | # Handles extracting the 3 types of bitmaps used by RollerCoaster Tycoon: 40 | # 1. Direct bitmaps consisting of color palette entries 41 | # 2. Compact bitmaps consisting of "scan line" rows of both transparent and 42 | # color pixels 43 | # 3. Color palettes consisting of RGBQUAD structures 44 | @dataclass 45 | class BITMAP: 46 | FLAG_DIRECT = 0x1 47 | FLAG_COMPACT = 0x5 48 | FLAG_PALETTE = 0x8 49 | MODE_RGB = "RGB" 50 | width: int 51 | height: int 52 | data: List[Tuple[int, int, int]] 53 | filename: str 54 | 55 | @classmethod 56 | def generate(cls, tgrecord, palette, color_table_data): 57 | generators = { 58 | BITMAP.FLAG_PALETTE: BITMAP.generate_palette, 59 | BITMAP.FLAG_COMPACT: BITMAP.generate_compact, 60 | BITMAP.FLAG_DIRECT: BITMAP.generate_direct, 61 | } 62 | try: 63 | return generators[tgrecord.Flags]( 64 | tgrecord, palette, color_table_data 65 | ) 66 | except KeyError: 67 | raise Exception( 68 | f"Error unpacking BITMAP: Unknown flag {tgrecord.Flags}" 69 | ) 70 | 71 | # Extract direct bitmaps, which simply consist of indices into the color 72 | # palette 73 | @classmethod 74 | def generate_direct(cls, tgrecord, palette, color_table_data): 75 | try: 76 | color_data = color_table_data[ 77 | tgrecord.StartAddress : tgrecord.StartAddress 78 | + (tgrecord.Width * tgrecord.Height) 79 | ] 80 | return cls( 81 | tgrecord.Width, 82 | tgrecord.Height, 83 | [palette[idx] for idx in color_data], 84 | f"{tgrecord.StartAddress}_{hex(tgrecord.StartAddress)}.bmp", 85 | ) 86 | except Exception: 87 | raise Exception("Error unpacking direct BITMAP") 88 | 89 | # Extract compact bitmaps, which consist of a table of "scan line" rows 90 | # and their elements 91 | @classmethod 92 | def generate_compact(cls, tgrecord, palette, color_table_data): 93 | TRANSPARENT_ELEMENT = (0, 0, 0) 94 | try: 95 | # Read the header containing the offsets to each scan line row 96 | # This is 2 x the height of the image as each offset is 2 bytes 97 | table_header = color_table_data[ 98 | tgrecord.StartAddress : tgrecord.StartAddress 99 | + (2 * tgrecord.Height) 100 | ] 101 | # iter_unpack returns a tuple, of which we only need the first 102 | # element 103 | table_row_offsets = [ 104 | offset[0] for offset in iter_unpack(" None: 39 | try: 40 | # Find pid by process name 41 | self.pid = next( 42 | ( 43 | proc.pid 44 | for proc in process_iter() 45 | if proc.name() == process_name 46 | ) 47 | ) 48 | self.process_name = process_name 49 | except StopIteration: 50 | raise Exception(f"Process '{process_name}' not found.") 51 | 52 | # Read num_bytes bytes from address within the desired process's memory 53 | def read_memory(self, address, num_bytes): 54 | try: 55 | self.process_handle = ctypes.windll.kernel32.OpenProcess( 56 | self.PROCESS_VM_READ, False, self.pid 57 | ) 58 | if self.process_handle is None: 59 | raise Exception(f"Failed to open process {self.process_name}") 60 | buffer = ctypes.create_string_buffer(num_bytes) 61 | bytes_read = ctypes.c_ulong(0) 62 | ctypes.windll.kernel32.ReadProcessMemory( 63 | self.process_handle, 64 | address, 65 | buffer, 66 | ctypes.sizeof(buffer), 67 | ctypes.byref(bytes_read), 68 | ) 69 | ctypes.windll.kernel32.CloseHandle(self.process_handle) 70 | return buffer.raw[: bytes_read.value] 71 | except Exception: 72 | raise Exception(f"Failed to read from {hex(address)}") 73 | -------------------------------------------------------------------------------- /rct_horror_mod/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeFF0Falltrades/Tutorials/c9347157e74f914353ba793c9184780524dd67f6/rct_horror_mod/utils/__init__.py -------------------------------------------------------------------------------- /rct_horror_mod/utils/bitmap_extract.py: -------------------------------------------------------------------------------- 1 | # Author: jeFF0Falltrades 2 | 3 | # From the video "Modding RollerCoaster Tycoon into a Peele Horror Film": 4 | 5 | # GitHub: https://github.com/jeFF0Falltrades/Tutorials/tree/master/rct_horror_mod 6 | 7 | # YouTube: https://www.youtube.com/watch?v=1MOrjGZ4hbo 8 | 9 | # MIT License 10 | 11 | # Copyright (c) 2023 Jeff Archer 12 | 13 | # Permission is hereby granted, free of charge, to any person obtaining a copy 14 | # of this software and associated documentation files (the "Software"), to deal 15 | # in the Software without restriction, including without limitation the rights 16 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | # copies of the Software, and to permit persons to whom the Software is 18 | # furnished to do so, subject to the following conditions: 19 | 20 | # The above copyright notice and this permission notice shall be included in all 21 | # copies or substantial portions of the Software. 22 | 23 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | # SOFTWARE. 30 | import logging 31 | from concurrent.futures import ThreadPoolExecutor 32 | from functools import partial 33 | from os import mkdir 34 | from os.path import isdir, join 35 | from PIL import Image 36 | from struct import iter_unpack 37 | from structs.bitmap import BITMAP 38 | from structs.tgraphicrecord import TGraphicRecord 39 | 40 | 41 | # Thread-safe function for extracting a single bitmap given its record data from 42 | # CSG1i.dat, a color palette to interpret the bitmap's colors, an output path 43 | # to write the resulting bitmap, and the color entry data from CSG1.dat 44 | def bitmap_dump_thread_safe(bmp_record, palette, path_output, raw_csg1_data): 45 | try: 46 | bmp = BITMAP.generate(bmp_record, palette, raw_csg1_data) 47 | img = Image.new(BITMAP.MODE_RGB, (bmp.width, bmp.height)) 48 | img.putdata(bmp.data) 49 | img.save(join(path_output, bmp.filename)) 50 | return True 51 | except Exception as e: 52 | logging.exception( 53 | f"Error unpacking bitmap at {bmp_record.StartAddress}\n{e}" 54 | ) 55 | return False 56 | 57 | 58 | # Extract bitmaps, given the relevant data from CSG1.dat and CSG1i.dat and an 59 | # output directory 60 | def bitmap_extract(args): 61 | if not isdir(args.path_outdir): 62 | logging.info( 63 | f"No directory found at {args.path_outdir}, so we made one..." 64 | ) 65 | mkdir(args.path_outdir) 66 | 67 | # Open and parse the color palette file 68 | logging.info(f"Parsing palette from {args.path_palette}...") 69 | img = Image.open(args.path_palette).convert(BITMAP.MODE_RGB) 70 | palette = list(img.getdata()) 71 | 72 | # Load bitmap records from CSG1i.dat into a list 73 | logging.info(f"Parsing TGraphicRecords from {args.path_csg1i}...") 74 | with open(args.path_csg1i, "rb") as infile: 75 | bitmap_records = [ 76 | TGraphicRecord.generate(record_data) 77 | for record_data in iter_unpack( 78 | TGraphicRecord.FORMAT_STR, infile.read() 79 | ) 80 | ] 81 | 82 | logging.info(f"Extracting bitmaps from {args.path_csg1}...") 83 | with open(args.path_csg1, "rb") as infile: 84 | raw_csg1_data = infile.read() 85 | 86 | success_count = 0 87 | 88 | # Start a new thread calling bitmap_dump_thread_safe() for each record 89 | # WARNING: There are ~70K bitmaps packed into the RCT Gold Edition 90 | # CSG1i.dat file, so this can be time-/resource-intensive 91 | with ThreadPoolExecutor() as pool: 92 | success_count = len( 93 | [ 94 | result 95 | for result in pool.map( 96 | partial( 97 | bitmap_dump_thread_safe, 98 | palette=palette, 99 | path_output=args.path_outdir, 100 | raw_csg1_data=raw_csg1_data, 101 | ), 102 | bitmap_records, 103 | ) 104 | if result 105 | ] 106 | ) 107 | 108 | logging.info( 109 | f"Successfully extracted {success_count} of" 110 | f" {len(bitmap_records)} bitmaps to {args.path_outdir}" 111 | ) 112 | -------------------------------------------------------------------------------- /rct_horror_mod/utils/dib_palette_dump.py: -------------------------------------------------------------------------------- 1 | # Author: jeFF0Falltrades 2 | 3 | # From the video "Modding RollerCoaster Tycoon into a Peele Horror Film": 4 | 5 | # GitHub: https://github.com/jeFF0Falltrades/Tutorials/tree/master/rct_horror_mod 6 | 7 | # YouTube: https://www.youtube.com/watch?v=1MOrjGZ4hbo 8 | 9 | # MIT License 10 | 11 | # Copyright (c) 2023 Jeff Archer 12 | 13 | # Permission is hereby granted, free of charge, to any person obtaining a copy 14 | # of this software and associated documentation files (the "Software"), to deal 15 | # in the Software without restriction, including without limitation the rights 16 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | # copies of the Software, and to permit persons to whom the Software is 18 | # furnished to do so, subject to the following conditions: 19 | 20 | # The above copyright notice and this permission notice shall be included in all 21 | # copies or substantial portions of the Software. 22 | 23 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | # SOFTWARE. 30 | import logging 31 | from pprint import pformat 32 | from utils.process_dumper import ProcessDumper 33 | 34 | 35 | # Offset to the BITMAPINFO struct pointer following the DIB 36 | OFFSET_PTR_BITMAPINFO = 0x88 37 | # Virtual address of the pointer to the DIB pointer 38 | PTR_PTR_DIB = 0x008BCDC4 39 | 40 | # Virtual address of the current window resolution 41 | PTR_RESOLUTION_X = 0x00C3E2E4 42 | PTR_RESOLUTION_Y = 0x00C3E854 43 | TARGET_PROCESS = "RCT.EXE" 44 | 45 | 46 | # Show or extract the current DIB and color palette from an active RCT process 47 | def dib_palette_dump(args): 48 | show = args.command == "show" 49 | path_palette = args.path_palette if "path_palette" in args else None 50 | path_dib = args.path_dib if "path_dib" in args else None 51 | 52 | # Pass in relevant data to the ProcessDumper class 53 | rct_process_dumper = ProcessDumper( 54 | TARGET_PROCESS, 55 | PTR_PTR_DIB, 56 | OFFSET_PTR_BITMAPINFO, 57 | PTR_RESOLUTION_X, 58 | PTR_RESOLUTION_Y, 59 | ) 60 | 61 | # Read back the data collected by ProcessDumper 62 | logging.info(f"DIB Located at: {hex(rct_process_dumper.ptr_dib)}\n") 63 | logging.info( 64 | f"BITMAPINFO located at: {hex(rct_process_dumper.ptr_bitmapinfo)}\n" 65 | ) 66 | logging.info( 67 | "Parsed the following" 68 | f" BITMAPINFOHEADER:\n{pformat(vars(rct_process_dumper.bitmap_info_header))}\n" 69 | ) 70 | logging.info( 71 | f"Color Table located at: {hex(rct_process_dumper.ptr_color_table)}\n" 72 | ) 73 | logging.info( 74 | f"Resolution: {rct_process_dumper.resolution_x} x" 75 | f" {rct_process_dumper.resolution_y}\n" 76 | ) 77 | 78 | # Generate the active color palette and DIB 79 | logging.info("Generating palette...\n") 80 | rct_process_dumper.generate_color_palette( 81 | show_only=show, save_path=path_palette 82 | ) 83 | logging.info("Generating DIB...\n") 84 | rct_process_dumper.generate_dib(show_only=show, save_path=path_dib) 85 | -------------------------------------------------------------------------------- /rct_horror_mod/utils/process_dumper.py: -------------------------------------------------------------------------------- 1 | # Author: jeFF0Falltrades 2 | 3 | # From the video "Modding RollerCoaster Tycoon into a Peele Horror Film": 4 | 5 | # GitHub: https://github.com/jeFF0Falltrades/Tutorials/tree/master/rct_horror_mod 6 | 7 | # YouTube: https://www.youtube.com/watch?v=1MOrjGZ4hbo 8 | 9 | # MIT License 10 | 11 | # Copyright (c) 2023 Jeff Archer 12 | 13 | # Permission is hereby granted, free of charge, to any person obtaining a copy 14 | # of this software and associated documentation files (the "Software"), to deal 15 | # in the Software without restriction, including without limitation the rights 16 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | # copies of the Software, and to permit persons to whom the Software is 18 | # furnished to do so, subject to the following conditions: 19 | 20 | # The above copyright notice and this permission notice shall be included in all 21 | # copies or substantial portions of the Software. 22 | 23 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | # SOFTWARE. 30 | import struct 31 | 32 | from structs.bitmap import BITMAP 33 | from structs.bitmapinfoheader import BITMAPINFOHEADER 34 | from PIL import Image 35 | from structs.rgbquad import RGBQUAD 36 | from structs.windows_process import WindowsProcess 37 | 38 | 39 | # Helper class to handle parsing graphics data from an active RCT process 40 | class ProcessDumper: 41 | # 16x16 gives us a nice square bitmap of the active palette 42 | PALETTE_SIZE = (16, 16) 43 | 44 | def __init__( 45 | self, 46 | process_name, 47 | ptr_ptr_dib, 48 | offset_to_ptr_bitmapinfo, 49 | ptr_resolution_x, 50 | ptr_resolution_y, 51 | ) -> None: 52 | # Open the process and unpack values for the DIB and BITMAPINFOHEADER 53 | # pointers 54 | self.target_process = WindowsProcess(process_name) 55 | self.ptr_ptr_dib = self.unpack_from_proc_memory(ptr_ptr_dib) 56 | self.ptr_dib = self.unpack_from_proc_memory(self.ptr_ptr_dib) 57 | self.ptr_bitmapinfo = self.unpack_from_proc_memory( 58 | self.ptr_ptr_dib + offset_to_ptr_bitmapinfo 59 | ) 60 | 61 | # Extract the BITMAPINFOHEADER struct 62 | self.bitmap_info_header = BITMAPINFOHEADER.generate( 63 | self.unpack_from_proc_memory( 64 | self.ptr_bitmapinfo, 65 | BITMAPINFOHEADER.SIZE, 66 | BITMAPINFOHEADER.FORMAT_STR, 67 | ) 68 | ) 69 | # Extract the color table pointer 70 | self.ptr_color_table = self.ptr_bitmapinfo + BITMAPINFOHEADER.SIZE 71 | 72 | # Build the color palette and DIB 73 | self.color_palette = self.build_color_palette() 74 | self.resolution_x, self.resolution_y = self.unpack_from_proc_memory( 75 | ptr_resolution_x 76 | ), self.unpack_from_proc_memory(ptr_resolution_y) 77 | self.dib = self.build_dib() 78 | 79 | # Build the active color palette as a list of RGBQUAD tuples 80 | def build_color_palette(self): 81 | color_table_size = ( 82 | self.bitmap_info_header.biClrUsed 83 | if self.bitmap_info_header.biClrUsed != 0 84 | else 2**self.bitmap_info_header.biBitCount 85 | ) 86 | return [ 87 | RGBQUAD.generate(tup).as_tuple() 88 | for tup in self.unpack_from_proc_memory( 89 | self.ptr_color_table, 90 | color_table_size * RGBQUAD.SIZE, 91 | RGBQUAD.FORMAT_STR, 92 | True, 93 | ) 94 | ] 95 | 96 | # Build the DIB by reading in the color indices and mapping them within the 97 | # active color palette 98 | def build_dib(self): 99 | dib_data = self.target_process.read_memory( 100 | self.ptr_dib, 101 | ( 102 | abs( 103 | self.bitmap_info_header.biWidth 104 | * self.bitmap_info_header.biHeight 105 | ) 106 | ), 107 | ) 108 | return [self.color_palette[idx] for idx in dib_data] 109 | 110 | # Generate a bitmap image of the active color palette 111 | def generate_color_palette(self, save_path="palette.bmp", show_only=False): 112 | self.generate_img( 113 | ProcessDumper.PALETTE_SIZE, 114 | self.color_palette, 115 | save_path=save_path, 116 | show_only=show_only, 117 | ) 118 | 119 | # Generate a bitmap image of the active DIB being displayed 120 | def generate_dib(self, save_path="dib.bmp", show_only=False): 121 | # Crop needed here to crop out negative space 122 | self.generate_img( 123 | ( 124 | self.bitmap_info_header.biWidth, 125 | abs(self.bitmap_info_header.biHeight), 126 | ), 127 | self.dib, 128 | crop=(0, 0, self.resolution_x, self.resolution_y), 129 | save_path=save_path, 130 | show_only=show_only, 131 | ) 132 | 133 | # Show or save a bitmap image, given its pixel data 134 | def generate_img( 135 | self, 136 | img_size, 137 | img_data, 138 | crop=None, 139 | save_path="img.bmp", 140 | show_only=False, 141 | ): 142 | img = Image.new(BITMAP.MODE_RGB, img_size) 143 | img.putdata(img_data) 144 | if crop is not None: 145 | img = img.crop(crop) 146 | if show_only: 147 | img.show() 148 | else: 149 | img.save(save_path) 150 | 151 | # Unpack binary data from a WindowsProcess object 152 | def unpack_from_proc_memory( 153 | self, address, num_bytes=4, format_string=" 50 | # call eax 51 | REPLACEMENT_PATCH_CALL = ("FF 14 AD A4 70 42 00", "B8 %s FF D0") 52 | 53 | # Find a location in DATASEG segment of the executable with enough 54 | # null bytes to write our shellcode, which becomes: 55 | # 56 | # call ds:funcs_408559[ebp*4] (original call we overwrote) 57 | # pusha (save all registers to the stack to restore after mod) 58 | # push 0 59 | # push 0 60 | # push 0 61 | # push offset 62 | # call ds:mciSendStringA (open audio WAV with mciSendString) 63 | # push 0 64 | # push 0 65 | # push 0 66 | # push offset aPlayHorrorTo15 ; "play horror to 15000" 67 | # call ds:mciSendStringA (play first 15 seconds of audio track) 68 | # mov eax, offset sub_438B96 69 | # call eax ; sub_438B96 (call "applause" function) 70 | # popa (restore the original register states from the stack) 71 | # retn (return to the regular program flow) 72 | REPLACEMENT_SHELLCODE = ( 73 | "03 00 02 00 01 00" + "00" * 80, 74 | ( 75 | "FF 14 AD A4 70 42 00 60 6A 00 6A 00 6A 00 68 %s FF 15 14 B3 78 00 6A" 76 | " 00 6A 00 6A 00 68 %s FF 15 14 B3 78 00 B8 96 8B 43 00 FF D0 61 C3" 77 | ), 78 | ) 79 | 80 | # "open type waveaudio alias horror" 81 | MCISENDSTR_OPEN = ( 82 | "6F 70 65 6E 20 22 %s 22 20 74 79 70 65 20 77 61 76 65 61 75 64 69 6F 20 61" 83 | " 6C 69 61 73 20 68 6F 72 72 6F 72 00" 84 | ) 85 | # "play horror to 15000" 86 | MCISENDSTR_PLAY = ( 87 | "70 6C 61 79 20 68 6F 72 72 6F 72 20 74 6F 20 31 35 30 30 30 00" 88 | ) 89 | SECTION_DATASEG = "DATASEG" 90 | 91 | 92 | # Since we'll be re-using the same data and a lot of offsets/addresses, it was # easier to put the patch functionality into its own class 93 | class RCTHorrorPatcher: 94 | def __init__( 95 | self, infile, outfile, replacement_bitmap, selected_audio_track 96 | ): 97 | self.address_shellcode = -1 98 | self.data = None 99 | self.infile = infile 100 | try: 101 | with open(self.infile, "rb") as infile: 102 | self.data = infile.read() 103 | except FileNotFoundError: 104 | raise Exception(f"Valid input file not found at {self.infile}") 105 | self.offset_bitmap = -1 106 | self.offset_shellcode = -1 107 | self.outfile = outfile 108 | self.replacement_bitmap = replacement_bitmap 109 | self.selected_audio_track = selected_audio_track 110 | 111 | # Convert a byte-string to a hex string like those shown above, e.g. 112 | # b'\xaa\xbb\xcc\xdd' becomes "AA BB CC DD" 113 | # 114 | # Used with pack(), we can convert to little-endian as well, e.g. 115 | # byte_to_hex_str(pack(" "DD CC BB AA" 116 | def byte_to_hex_str(self, byte_str): 117 | return " ".join(f"{byte:02X}" for byte in byte_str) 118 | 119 | # Calculate the address we should call to run our shellcode by converting 120 | # the file offset of the shellcode to a Virtual Address; For this we use the 121 | # pefile library 122 | def calculate_shellcode_addr(self): 123 | pe = PE(self.infile) 124 | image_base = pe.OPTIONAL_HEADER.ImageBase 125 | for section in pe.sections: 126 | if section.Name.strip(b"\x00").decode("utf-8") == SECTION_DATASEG: 127 | # Virtual Address = file offset - file offset to beginning of 128 | # section + section's Virtual Address + the base address of 129 | # the PE 130 | self.address_shellcode = ( 131 | self.offset_shellcode 132 | - section.PointerToRawData 133 | + section.VirtualAddress 134 | + image_base 135 | ) 136 | logging.info( 137 | f"Shellcode offset {self.offset_shellcode} translated to Virtual" 138 | f" Address {hex(self.address_shellcode)}" 139 | ) 140 | 141 | # Get the length of a hex string like those shown above - removing any 142 | # formatting placeholders and space, and dividing by 2 since each byte 143 | # takes 2 chars 144 | def len_hex_string(self, hex_str): 145 | return int( 146 | len((hex_str % tuple(" " * hex_str.count("%s"))).replace(" ", "")) 147 | / 2 148 | ) 149 | 150 | # Make a bad pun in the window title (because we can) 151 | def make_bad_pun(self): 152 | self.data = self.data.replace( 153 | b"\x00\x00\x00RollerCoaster Tycoon\x00\x00\x00", 154 | b"\x00\x00\x00RollerCoaster Diecoon\x00\x00", 155 | ) 156 | 157 | # Since Python strings are immutable, we have to re-assign self.data every 158 | # time we want to modify it; Here, we use join() and string splitting to do 159 | # a sort of "in-place" insertion at the offset we wish to overwrite 160 | def overwrite_data(self, offset, new_data): 161 | self.data = new_data.join( 162 | [self.data[:offset], self.data[offset + len(new_data) :]] 163 | ) 164 | 165 | # Do ALL the things (to successfully patch the executable) 166 | def patch(self): 167 | self.replace_explosion_bitmap() 168 | self.write_shellcode() 169 | self.patch_call_instruction() 170 | self.make_bad_pun() 171 | self.write_output_file() 172 | 173 | # Patch the instruction "call ds:funcs_408559[ebp*4]" in the block where 174 | # the explosion animation is played, and replace it with a call to our mod 175 | # shellcode 176 | def patch_call_instruction(self): 177 | # Find the location of the call to replace by looking for the opcode 178 | # for "call ds:funcs_408559[ebp*4]" closest to where the explosion 179 | # bitmap was replaced 180 | offset_patch = self.data.find( 181 | bytes.fromhex(REPLACEMENT_PATCH_CALL[0]), 182 | self.offset_bitmap, 183 | ) 184 | self.overwrite_data( 185 | offset_patch, 186 | bytes.fromhex( 187 | REPLACEMENT_PATCH_CALL[1] 188 | % self.byte_to_hex_str(pack("> 4) 201 | ) 202 | except Exception: 203 | raise Exception( 204 | "Error unpacking bitmap offset value:" 205 | f" {self.replacement_bitmap}" 206 | ) 207 | self.offset_bitmap = self.data.find( 208 | bytes.fromhex(REPLACEMENT_BITMAP[0]) 209 | ) 210 | if self.offset_bitmap == -1: 211 | raise Exception("Could not find explosion bitmap offset") 212 | self.overwrite_data( 213 | self.offset_bitmap, 214 | bytes.fromhex(REPLACEMENT_BITMAP[1] % bitmap_bytes), 215 | ) 216 | logging.info( 217 | "Successfully replaced explosion bitmap with bitmap at offset" 218 | f" 0x{self.replacement_bitmap}" 219 | ) 220 | 221 | # Write the modified binary data to the desired output file 222 | def write_output_file(self): 223 | try: 224 | with open(self.outfile, "wb") as outfile: 225 | outfile.write(self.data) 226 | except Exception: 227 | raise Exception(f"Error writing patched data to {self.outfile}") 228 | logging.info(f"Wrote output file to {self.outfile}") 229 | 230 | # Write the mod shellcode to the DATASEG section 231 | def write_shellcode(self): 232 | # Add 8 to skip over \x03\x00...\x00\x00 at shellcode region 233 | self.offset_shellcode = ( 234 | self.data.find(bytes.fromhex(REPLACEMENT_SHELLCODE[0])) + 8 235 | ) 236 | if self.offset_shellcode == -1: 237 | raise Exception("Could not find shellcode placeholder offset") 238 | 239 | # Insert desired audio track path into the mciSendString "open" command 240 | mciss_open = MCISENDSTR_OPEN % self.byte_to_hex_str( 241 | self.selected_audio_track.encode("utf-8") 242 | ) 243 | logging.info( 244 | f"Successfully set audio track to {self.selected_audio_track}" 245 | ) 246 | 247 | # Calculate the Virtual Address of where our shellcode will go 248 | self.calculate_shellcode_addr() 249 | 250 | # Address of our mciSendString "open" command string will be the 251 | # address directly after our shellcode (add 8 bytes here to account for 252 | # the two 4-byte addresses of the two mciSendString commands we will 253 | # insert into the shellcode) 254 | addr_mciss_open = ( 255 | self.address_shellcode 256 | + self.len_hex_string(REPLACEMENT_SHELLCODE[1]) 257 | + 8 258 | ) 259 | 260 | # The mciSendString "play" command will go right after the "open" 261 | # command 262 | addr_mciss_play = addr_mciss_open + self.len_hex_string(mciss_open) 263 | 264 | # Put the shellcode and mciSendString commands together and we got 265 | # ourselves a complete mod! 266 | shellcode = ( 267 | REPLACEMENT_SHELLCODE[1] 268 | % ( 269 | self.byte_to_hex_str(pack("