├── .gitignore ├── .gitmodules ├── .venv ├── LICENSE ├── Pipfile ├── Pipfile.lock ├── README.md ├── __init__.py ├── apps ├── __init__.py ├── apis │ ├── __init__.py │ ├── api.py │ └── router.py └── jobs │ ├── __init__.py │ ├── asyncs │ ├── __init__.py │ ├── analysis │ │ └── __init__.py │ └── process │ │ └── __init__.py │ └── periodics │ └── __init__.py ├── bin └── Panda.sh ├── data ├── monitor │ └── latest │ │ ├── inject-x64.exe │ │ ├── inject-x86.exe │ │ ├── is32bit.exe │ │ ├── monitor-x64.dll │ │ └── monitor-x86.dll └── rules │ ├── whitelist │ ├── domain.txt │ ├── ip.txt │ ├── mispdomain.txt │ ├── misphash.txt │ ├── mispip.txt │ └── mispurl.txt │ └── yara │ ├── binaries │ ├── embedded.yar │ ├── shellcodes.yar │ └── vmdetect.yar │ ├── dumpmem │ └── readme_first.txt │ ├── memory │ └── .gitignore │ ├── office │ └── .gitignore │ ├── scripts │ └── .gitignore │ ├── shellcode │ └── .gitignore │ └── urls │ └── .gitignore ├── docs ├── HTML │ └── .gitkeep ├── MD │ └── .gitkeep ├── PDF │ └── .gitkeep ├── RST │ └── .gitkeep └── sponsor │ ├── alipay.jpg │ └── wechat.jpg ├── etc ├── Panda.service ├── conf.d │ ├── .gitkeep │ └── custom.yaml ├── default.yaml ├── example.yaml └── plugin.d │ └── .gitkeep ├── lib ├── __init__.py ├── base │ ├── Application.py │ ├── Machinery.py │ └── __init__.py ├── common │ ├── __init__.py │ └── config.py ├── core │ ├── __init__.py │ ├── backend │ │ └── __init__.py │ ├── manager │ │ ├── GuestManager.py │ │ ├── ResultManager.py │ │ └── __init__.py │ └── service │ │ └── __init__.py ├── defines │ ├── __init__.py │ ├── context.py │ ├── defines.py │ └── types.py ├── exceptions │ ├── __init__.py │ ├── critical.py │ └── operation.py ├── implements │ ├── __init__.py │ ├── celery_app.py │ └── flask_app.py ├── main.py ├── objects │ └── __init__.py ├── settings.py └── utils │ ├── __init__.py │ ├── color.py │ └── utils.py ├── sandbox ├── __init__.py └── celery.py ├── scripts └── .gitkeep ├── staff ├── android │ ├── anti-vm │ │ ├── fake-build.prop │ │ ├── fake-cpuinfo │ │ └── fake-drivers │ ├── apps │ │ ├── ImportContacts.apk │ │ ├── Superuser.apk │ │ └── de.robv.android.xposed.installer_v33_36570c.apk │ ├── binaries │ │ └── su │ ├── create_guest_avd.sh │ └── hooking │ │ ├── Droidmon.apk │ │ └── EmulatorAntiDetect.apk ├── darwin │ ├── bootstrap_guest.sh │ └── bootstrap_host.sh └── windows │ ├── create_guest_win7.sh │ └── qemu │ ├── android.xml │ ├── default.xml │ ├── linux.xml │ └── windows.xml └── tests ├── __init__.py ├── unittest_agent.py ├── unittest_setting.py └── unittest_virtualbox.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | .eggs/ 12 | var/ 13 | wheels/ 14 | *.egg-info/ 15 | .installed.cfg 16 | *.egg 17 | MANIFEST 18 | 19 | # PyInstaller 20 | # Usually these files are written by a python script from a template 21 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 22 | *.manifest 23 | *.spec 24 | 25 | # Installer logs 26 | pip-log.txt 27 | pip-delete-this-directory.txt 28 | 29 | # Unit test / coverage reports 30 | htmlcov/ 31 | .tox/ 32 | .coverage 33 | .coverage.* 34 | .cache 35 | nosetests.xml 36 | coverage.xml 37 | *.cover 38 | .hypothesis/ 39 | .pytest_cache/ 40 | 41 | # Translations 42 | *.mo 43 | *.pot 44 | 45 | # Celery Schedule File 46 | celerybeat-schedule 47 | 48 | # Environments 49 | venv/ 50 | 51 | # Jetrain Environments Settings 52 | .idea/ 53 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "data/analyzer"] 2 | path = data/analyzer 3 | url = https://github.com/Ryuchen/Panda-Sandbox-Analyzer.git 4 | [submodule "plugins"] 5 | path = plugins 6 | url = https://github.com/Ryuchen/Panda-Sandbox-Plugins.git 7 | [submodule "web"] 8 | path = web 9 | url = https://github.com/Ryuchen/Panda-Sandbox-Web.git 10 | [submodule "data/agent"] 11 | path = data/agent 12 | url = https://github.com/Ryuchen/Panda-Sandbox-Agent.git 13 | -------------------------------------------------------------------------------- /.venv: -------------------------------------------------------------------------------- 1 | ./venv -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Ryuchen 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 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://mirrors.aliyun.com/pypi/simple/" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | 8 | [packages] 9 | requests = "*" 10 | certifi = "*" 11 | chardet = "*" 12 | click = "*" 13 | redis = "*" 14 | celery = "*" 15 | pyyaml = "*" 16 | gevent = "*" 17 | flask = "*" 18 | pycodestyle = "*" 19 | 20 | [requires] 21 | python_version = "3.7" 22 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "2c35e4df2c67cbcd231deb40d92ec1ffc330c8f6d1fb665b0c6aa58f8f8e8283" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.7" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://mirrors.aliyun.com/pypi/simple/", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "amqp": { 20 | "hashes": [ 21 | "sha256:aa4409446139676943a2eaa27d5f58caf750f4ca5a89f888c452afd86be6a67d", 22 | "sha256:cbb6f87d53cac612a594f982b717cc1c54c6a1e17943a0a0d32dc6cc9e2120c8" 23 | ], 24 | "version": "==2.5.0" 25 | }, 26 | "billiard": { 27 | "hashes": [ 28 | "sha256:756bf323f250db8bf88462cd042c992ba60d8f5e07fc5636c24ba7d6f4261d84" 29 | ], 30 | "version": "==3.6.0.0" 31 | }, 32 | "celery": { 33 | "hashes": [ 34 | "sha256:4c4532aa683f170f40bd76f928b70bc06ff171a959e06e71bf35f2f9d6031ef9", 35 | "sha256:528e56767ae7e43a16cfef24ee1062491f5754368d38fcfffa861cdb9ef219be" 36 | ], 37 | "index": "pypi", 38 | "version": "==4.3.0" 39 | }, 40 | "certifi": { 41 | "hashes": [ 42 | "sha256:046832c04d4e752f37383b628bc601a7ea7211496b4638f6514d0e5b9acc4939", 43 | "sha256:945e3ba63a0b9f577b1395204e13c3a231f9bc0223888be653286534e5873695" 44 | ], 45 | "index": "pypi", 46 | "version": "==2019.6.16" 47 | }, 48 | "chardet": { 49 | "hashes": [ 50 | "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", 51 | "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" 52 | ], 53 | "index": "pypi", 54 | "version": "==3.0.4" 55 | }, 56 | "click": { 57 | "hashes": [ 58 | "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", 59 | "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7" 60 | ], 61 | "index": "pypi", 62 | "version": "==7.0" 63 | }, 64 | "flask": { 65 | "hashes": [ 66 | "sha256:ad7c6d841e64296b962296c2c2dabc6543752985727af86a975072dea984b6f3", 67 | "sha256:e7d32475d1de5facaa55e3958bc4ec66d3762076b074296aa50ef8fdc5b9df61" 68 | ], 69 | "index": "pypi", 70 | "version": "==1.0.3" 71 | }, 72 | "gevent": { 73 | "hashes": [ 74 | "sha256:0774babec518a24d9a7231d4e689931f31b332c4517a771e532002614e270a64", 75 | "sha256:0e1e5b73a445fe82d40907322e1e0eec6a6745ca3cea19291c6f9f50117bb7ea", 76 | "sha256:0ff2b70e8e338cf13bedf146b8c29d475e2a544b5d1fe14045aee827c073842c", 77 | "sha256:107f4232db2172f7e8429ed7779c10f2ed16616d75ffbe77e0e0c3fcdeb51a51", 78 | "sha256:14b4d06d19d39a440e72253f77067d27209c67e7611e352f79fe69e0f618f76e", 79 | "sha256:1b7d3a285978b27b469c0ff5fb5a72bcd69f4306dbbf22d7997d83209a8ba917", 80 | "sha256:1eb7fa3b9bd9174dfe9c3b59b7a09b768ecd496debfc4976a9530a3e15c990d1", 81 | "sha256:2711e69788ddb34c059a30186e05c55a6b611cb9e34ac343e69cf3264d42fe1c", 82 | "sha256:28a0c5417b464562ab9842dd1fb0cc1524e60494641d973206ec24d6ec5f6909", 83 | "sha256:3249011d13d0c63bea72d91cec23a9cf18c25f91d1f115121e5c9113d753fa12", 84 | "sha256:44089ed06a962a3a70e96353c981d628b2d4a2f2a75ea5d90f916a62d22af2e8", 85 | "sha256:4bfa291e3c931ff3c99a349d8857605dca029de61d74c6bb82bd46373959c942", 86 | "sha256:50024a1ee2cf04645535c5ebaeaa0a60c5ef32e262da981f4be0546b26791950", 87 | "sha256:53b72385857e04e7faca13c613c07cab411480822ac658d97fd8a4ddbaf715c8", 88 | "sha256:74b7528f901f39c39cdbb50cdf08f1a2351725d9aebaef212a29abfbb06895ee", 89 | "sha256:7d0809e2991c9784eceeadef01c27ee6a33ca09ebba6154317a257353e3af922", 90 | "sha256:896b2b80931d6b13b5d9feba3d4eebc67d5e6ec54f0cf3339d08487d55d93b0e", 91 | "sha256:8d9ec51cc06580f8c21b41fd3f2b3465197ba5b23c00eb7d422b7ae0380510b0", 92 | "sha256:9f7a1e96fec45f70ad364e46de32ccacab4d80de238bd3c2edd036867ccd48ad", 93 | "sha256:ab4dc33ef0e26dc627559786a4fba0c2227f125db85d970abbf85b77506b3f51", 94 | "sha256:d1e6d1f156e999edab069d79d890859806b555ce4e4da5b6418616322f0a3df1", 95 | "sha256:d752bcf1b98174780e2317ada12013d612f05116456133a6acf3e17d43b71f05", 96 | "sha256:e5bcc4270671936349249d26140c267397b7b4b1381f5ec8b13c53c5b53ab6e1" 97 | ], 98 | "index": "pypi", 99 | "version": "==1.4.0" 100 | }, 101 | "greenlet": { 102 | "hashes": [ 103 | "sha256:000546ad01e6389e98626c1367be58efa613fa82a1be98b0c6fc24b563acc6d0", 104 | "sha256:0d48200bc50cbf498716712129eef819b1729339e34c3ae71656964dac907c28", 105 | "sha256:23d12eacffa9d0f290c0fe0c4e81ba6d5f3a5b7ac3c30a5eaf0126bf4deda5c8", 106 | "sha256:37c9ba82bd82eb6a23c2e5acc03055c0e45697253b2393c9a50cef76a3985304", 107 | "sha256:51503524dd6f152ab4ad1fbd168fc6c30b5795e8c70be4410a64940b3abb55c0", 108 | "sha256:8041e2de00e745c0e05a502d6e6db310db7faa7c979b3a5877123548a4c0b214", 109 | "sha256:81fcd96a275209ef117e9ec91f75c731fa18dcfd9ffaa1c0adbdaa3616a86043", 110 | "sha256:853da4f9563d982e4121fed8c92eea1a4594a2299037b3034c3c898cb8e933d6", 111 | "sha256:8b4572c334593d449113f9dc8d19b93b7b271bdbe90ba7509eb178923327b625", 112 | "sha256:9416443e219356e3c31f1f918a91badf2e37acf297e2fa13d24d1cc2380f8fbc", 113 | "sha256:9854f612e1b59ec66804931df5add3b2d5ef0067748ea29dc60f0efdcda9a638", 114 | "sha256:99a26afdb82ea83a265137a398f570402aa1f2b5dfb4ac3300c026931817b163", 115 | "sha256:a19bf883b3384957e4a4a13e6bd1ae3d85ae87f4beb5957e35b0be287f12f4e4", 116 | "sha256:a9f145660588187ff835c55a7d2ddf6abfc570c2651c276d3d4be8a2766db490", 117 | "sha256:ac57fcdcfb0b73bb3203b58a14501abb7e5ff9ea5e2edfa06bb03035f0cff248", 118 | "sha256:bcb530089ff24f6458a81ac3fa699e8c00194208a724b644ecc68422e1111939", 119 | "sha256:beeabe25c3b704f7d56b573f7d2ff88fc99f0138e43480cecdfcaa3b87fe4f87", 120 | "sha256:d634a7ea1fc3380ff96f9e44d8d22f38418c1c381d5fac680b272d7d90883720", 121 | "sha256:d97b0661e1aead761f0ded3b769044bb00ed5d33e1ec865e891a8b128bf7c656" 122 | ], 123 | "markers": "platform_python_implementation == 'CPython'", 124 | "version": "==0.4.15" 125 | }, 126 | "idna": { 127 | "hashes": [ 128 | "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", 129 | "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" 130 | ], 131 | "version": "==2.8" 132 | }, 133 | "itsdangerous": { 134 | "hashes": [ 135 | "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19", 136 | "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749" 137 | ], 138 | "version": "==1.1.0" 139 | }, 140 | "jinja2": { 141 | "hashes": [ 142 | "sha256:065c4f02ebe7f7cf559e49ee5a95fb800a9e4528727aec6f24402a5374c65013", 143 | "sha256:14dd6caf1527abb21f08f86c784eac40853ba93edb79552aa1e4b8aef1b61c7b" 144 | ], 145 | "version": "==2.10.1" 146 | }, 147 | "kombu": { 148 | "hashes": [ 149 | "sha256:55b71d3785def3470a16217fe0780f9e6f95e61bf9ad39ef8dce0177224eab77", 150 | "sha256:eb365ea795cd7e629ba2f1f398e0c3ba354b91ef4de225ffdf6ab45fdfc7d581" 151 | ], 152 | "version": "==4.6.3" 153 | }, 154 | "markupsafe": { 155 | "hashes": [ 156 | "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", 157 | "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", 158 | "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", 159 | "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", 160 | "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", 161 | "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", 162 | "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", 163 | "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", 164 | "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", 165 | "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", 166 | "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", 167 | "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", 168 | "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", 169 | "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", 170 | "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", 171 | "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", 172 | "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", 173 | "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", 174 | "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", 175 | "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", 176 | "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", 177 | "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", 178 | "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", 179 | "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", 180 | "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", 181 | "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", 182 | "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", 183 | "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7" 184 | ], 185 | "version": "==1.1.1" 186 | }, 187 | "pytz": { 188 | "hashes": [ 189 | "sha256:303879e36b721603cc54604edcac9d20401bdbe31e1e4fdee5b9f98d5d31dfda", 190 | "sha256:d747dd3d23d77ef44c6a3526e274af6efeb0a6f1afd5a69ba4d5be4098c8e141" 191 | ], 192 | "version": "==2019.1" 193 | }, 194 | "pyyaml": { 195 | "hashes": [ 196 | "sha256:57acc1d8533cbe51f6662a55434f0dbecfa2b9eaf115bede8f6fd00115a0c0d3", 197 | "sha256:588c94b3d16b76cfed8e0be54932e5729cc185caffaa5a451e7ad2f7ed8b4043", 198 | "sha256:68c8dd247f29f9a0d09375c9c6b8fdc64b60810ebf07ba4cdd64ceee3a58c7b7", 199 | "sha256:70d9818f1c9cd5c48bb87804f2efc8692f1023dac7f1a1a5c61d454043c1d265", 200 | "sha256:86a93cccd50f8c125286e637328ff4eef108400dd7089b46a7be3445eecfa391", 201 | "sha256:a0f329125a926876f647c9fa0ef32801587a12328b4a3c741270464e3e4fa778", 202 | "sha256:a3c252ab0fa1bb0d5a3f6449a4826732f3eb6c0270925548cac342bc9b22c225", 203 | "sha256:b4bb4d3f5e232425e25dda21c070ce05168a786ac9eda43768ab7f3ac2770955", 204 | "sha256:cd0618c5ba5bda5f4039b9398bb7fb6a317bb8298218c3de25c47c4740e4b95e", 205 | "sha256:ceacb9e5f8474dcf45b940578591c7f3d960e82f926c707788a570b51ba59190", 206 | "sha256:fe6a88094b64132c4bb3b631412e90032e8cfe9745a58370462240b8cb7553cd" 207 | ], 208 | "index": "pypi", 209 | "version": "==5.1.1" 210 | }, 211 | "redis": { 212 | "hashes": [ 213 | "sha256:6946b5dca72e86103edc8033019cc3814c031232d339d5f4533b02ea85685175", 214 | "sha256:8ca418d2ddca1b1a850afa1680a7d2fd1f3322739271de4b704e0d4668449273" 215 | ], 216 | "index": "pypi", 217 | "version": "==3.2.1" 218 | }, 219 | "requests": { 220 | "hashes": [ 221 | "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", 222 | "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31" 223 | ], 224 | "index": "pypi", 225 | "version": "==2.22.0" 226 | }, 227 | "urllib3": { 228 | "hashes": [ 229 | "sha256:b246607a25ac80bedac05c6f282e3cdaf3afb65420fd024ac94435cabe6e18d1", 230 | "sha256:dbe59173209418ae49d485b87d1681aefa36252ee85884c31346debd19463232" 231 | ], 232 | "version": "==1.25.3" 233 | }, 234 | "vine": { 235 | "hashes": [ 236 | "sha256:133ee6d7a9016f177ddeaf191c1f58421a1dcc6ee9a42c58b34bed40e1d2cd87", 237 | "sha256:ea4947cc56d1fd6f2095c8d543ee25dad966f78692528e68b4fada11ba3f98af" 238 | ], 239 | "version": "==1.3.0" 240 | }, 241 | "werkzeug": { 242 | "hashes": [ 243 | "sha256:865856ebb55c4dcd0630cdd8f3331a1847a819dda7e8c750d3db6f2aa6c0209c", 244 | "sha256:a0b915f0815982fb2a09161cb8f31708052d0951c3ba433ccc5e1aa276507ca6" 245 | ], 246 | "version": "==0.15.4" 247 | } 248 | }, 249 | "develop": {} 250 | } 251 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Welcome to Panda-Sandbox(钟馗沙箱)未完工

2 |

3 | 4 | 5 | 6 | License: MIT 7 |

8 | 9 | ### 🏠 [个人小栈](https://ryuchen.github.io/) 10 | 11 | > 未来更新的说明会刊登在这里,并且会不定时分享部分内容和心得 12 | > 我本身对于安全并不是很感冒,现在这个项目完全是出于兴趣,想把自己所学融汇贯通于一个完整的项目中,构建一个完善的、可交付的项目 13 | 14 | ### 📎 项目说明: 15 | > 目前Github社区中的开源沙箱系统,相较活跃的是 [cuckoosandbox](http://www.cuckoosandbox.org/),但是这个项目在过去的一年中变得更新较为缓慢,后续也未见其进行卓越性的改进,逐渐走向闭源。 16 | > 鉴于此,这个项目将初期集成和兼容 cuckoo 的 [monitor](https://github.com/cuckoosandbox/monitor) 和 [community](https://github.com/cuckoosandbox/community)。 17 | > 这是一个基于已经初见成熟的 cuckoo 开源版本的沙箱的修订版本,但将进行彻底的重新构建,后续该版本将彻底重构,能够完全适配国内软件环境。 18 | 19 | 20 | ### 🔥 设计特性: 21 | 22 | - 将收录和兼容 cuckoo 社区中的所有行为签名和检测手段。 23 | - 采用新的方式重构任务调度和沙箱调度,提高整体的运行效率。 24 | - 将集成开源情报,并将集成小型开源情报库在内,构建信誉体系。 25 | - 合并 cuckoo 开源插件中的 android、linux、macos 平台检测。 26 | - 重构 cuckoo 文件报告的展示效果,计划支持在 Web 界面制作虚拟机。 27 | - 将采用全新的设计方式实现多节点的支持,实现高效的集群能力。 28 | - 提供检测手段的算子化界面操作,支持在界面自定义文件检测流程和通过的检测手段。 29 | 30 | 31 | ### 👌 待办清单: 32 | * [x] 创建新的项目并创建第一次提交 33 | * [x] 迁移官方最新的 agent 脚本过来, 并将其改写为 Python3 的版本 34 | * [x] 测试改写后的 agent 脚本,使其功能可用 35 | * [x] 完成基本测试的 VMware Fusion Pro base on MacOS 的基本调用测试 36 | * [x] 构建 Readme.md 37 | 38 | * [x] 完成 Readme.md 文档的初步框架构建 39 | 40 | * [ ] 迁移官方 Guest.py 脚本过来,并将其改写为 Python3 的版本 41 | * [ ] 构建基本任务调度框架结构(celery) 42 | * [ ] 构建基本的虚拟机调用程序(VMWare Fusion Pro base on MacOS) 43 | 44 | 后续更新内容将采用 github 的看板功能进行维护 45 | 46 | 地址: v1.0.0-alpha [Project 看板](https://github.com/Ryuchen/Panda-Sandbox/projects/1) 47 | 48 | ### 📖 使用说明: 49 | 50 | ```python 51 | 52 | celery -A sandbox worker -l info -B -l INFO -E 53 | 54 | ``` 55 | 56 | ### 👤 作者介绍: 57 | 58 | Ryuchen ( 陈 浩 ) 59 | 60 | * Github: [https://github.com/Ryuchen](https://github.com/Ryuchen) 61 | * Email: [chenhaom1993@hotmail.com](chenhaom1993@hotmail.com) 62 | * QQ: 455480366 63 | * 微信: Chen_laws 64 | 65 | Nameplace ( 虚位以待 ) 66 | 67 | ### 🤝 贡献源码: 68 | 69 | Contributions, issues and feature requests are welcome!
Feel free to check [issues page](https://github.com/Ryuchen/Panda-Sandbox/issues). 70 | 71 | ### ⭐ 渴望支持: 72 | 73 | 如果你想继续观察 Panda-Sandbox 接下来的走向,请给我们一个 ⭐ 这是对于我们最大的鼓励。 74 | 此外,如果你觉得 Panda-Sandbox 对你有帮助,你可以赞助我们一杯咖啡,鼓励我们继续开发维护下去。 75 | 76 | | **微信** | **支付宝** | 77 | | ------------------------------- | ----------------------------------- | 78 | |

![扫码赞助](https://github.com/Ryuchen/Panda-Sandbox/raw/master/docs/sponsor/wechat.jpg)

|

![扫码赞助](https://github.com/Ryuchen/Panda-Sandbox/raw/master/docs/sponsor/alipay.jpg)

| 79 | 80 | ### 📝 开源协议: 81 | 82 | Copyright © 2019 [Ryuchen](https://github.com/Ryuchen).
83 | This project is [MIT](https://github.com/Ryuchen/Panda-Sandbox/raw/master/LICENSE) licensed. 84 | 请谨遵开源协议,谢谢使用! 85 | 86 | *** 87 | _This README was generated with ❤️ by [readme-md-generator](https://github.com/kefranabg/readme-md-generator)_ 88 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # ================================================== 4 | # @Time : 2019-05-30 20:24 5 | # @Author : ryuchen 6 | # @File : __init__.py.py 7 | # @Desc : 8 | # ================================================== -------------------------------------------------------------------------------- /apps/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # ================================================== 4 | # @Time : 2019-05-27 23:51 5 | # @Author : ryuchen 6 | # @Site : 7 | # @File : __init__.py.py 8 | # @Desc : 9 | # ================================================== -------------------------------------------------------------------------------- /apps/apis/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # ================================================== 4 | # @Time : 2019-06-20 11:08 5 | # @Author : ryuchen 6 | # @File : __init__.py.py 7 | # @Desc : 8 | # ================================================== -------------------------------------------------------------------------------- /apps/apis/api.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # ================================================== 4 | # @Time : 2019-06-20 14:18 5 | # @Author : ryuchen 6 | # @File : flask.py 7 | # @Desc : 8 | # ================================================== 9 | 10 | from flask import Flask 11 | 12 | app = Flask(__name__) 13 | -------------------------------------------------------------------------------- /apps/apis/router.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # ================================================== 4 | # @Time : 2019-06-20 14:27 5 | # @Author : ryuchen 6 | # @File : router.py 7 | # @Desc : 8 | # ================================================== 9 | 10 | from apps.apis.api import app 11 | 12 | 13 | @app.route('/') 14 | def hello_world(): 15 | return 'Hello, World!' 16 | -------------------------------------------------------------------------------- /apps/jobs/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # ================================================== 4 | # @Time : 2019-06-20 11:07 5 | # @Author : ryuchen 6 | # @File : __init__.py.py 7 | # @Desc : 8 | # ================================================== -------------------------------------------------------------------------------- /apps/jobs/asyncs/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # ================================================== 4 | # @Time : 2019-06-20 11:07 5 | # @Author : ryuchen 6 | # @File : __init__.py.py 7 | # @Desc : 8 | # ================================================== 9 | 10 | -------------------------------------------------------------------------------- /apps/jobs/asyncs/analysis/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # ================================================== 4 | # @Time : 2019-06-20 15:27 5 | # @Author : ryuchen 6 | # @File : __init__.py.py 7 | # @Desc : 8 | # ================================================== -------------------------------------------------------------------------------- /apps/jobs/asyncs/process/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # ================================================== 4 | # @Time : 2019-06-20 15:27 5 | # @Author : ryuchen 6 | # @File : __init__.py.py 7 | # @Desc : 8 | # ================================================== -------------------------------------------------------------------------------- /apps/jobs/periodics/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # ================================================== 4 | # @Time : 2019-06-20 11:08 5 | # @Author : ryuchen 6 | # @File : __init__.py.py 7 | # @Desc : 8 | # ================================================== -------------------------------------------------------------------------------- /bin/Panda.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash -------------------------------------------------------------------------------- /data/monitor/latest/inject-x64.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryuchen/Panda-Sandbox/09ae5da4b0ee4c688311208ce819dae82593490a/data/monitor/latest/inject-x64.exe -------------------------------------------------------------------------------- /data/monitor/latest/inject-x86.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryuchen/Panda-Sandbox/09ae5da4b0ee4c688311208ce819dae82593490a/data/monitor/latest/inject-x86.exe -------------------------------------------------------------------------------- /data/monitor/latest/is32bit.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryuchen/Panda-Sandbox/09ae5da4b0ee4c688311208ce819dae82593490a/data/monitor/latest/is32bit.exe -------------------------------------------------------------------------------- /data/monitor/latest/monitor-x64.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryuchen/Panda-Sandbox/09ae5da4b0ee4c688311208ce819dae82593490a/data/monitor/latest/monitor-x64.dll -------------------------------------------------------------------------------- /data/monitor/latest/monitor-x86.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryuchen/Panda-Sandbox/09ae5da4b0ee4c688311208ce819dae82593490a/data/monitor/latest/monitor-x86.dll -------------------------------------------------------------------------------- /data/rules/whitelist/domain.txt: -------------------------------------------------------------------------------- 1 | # You can add whitelisted domains here. 2 | java.com 3 | www.msn.com 4 | www.bing.com 5 | windows.microsoft.com 6 | go.microsoft.com 7 | static-hp-eas.s-msn.com 8 | img-s-msn-com.akamaized.net 9 | sdlc-esd.oracle.com 10 | javadl.sun.com 11 | res2.windows.microsoft.com 12 | res1.windows.microsoft.com 13 | img.s-msn.com 14 | js.microsoft.com 15 | fbstatic-a.akamaihd.net 16 | ajax.microsoft.com 17 | ajax.aspnetcdn.com 18 | ieonline.microsoft.com 19 | api.bing.com 20 | schemas.microsoft.com 21 | www.w3.org 22 | dns.msftncsi.com 23 | teredo.ipv6.microsoft.com 24 | time.windows.com 25 | www.msftncsi.com 26 | ocsp.msocsp.com 27 | ocsp.omniroot.com 28 | crl.microsoft.com 29 | www.msftconnecttest.com 30 | v10.vortex-win.data.microsoft.com 31 | settings-win.data.microsoft.com 32 | win10.ipv6.microsoft.com 33 | sls.update.microsoft.com 34 | fs.microsoft.com 35 | ctldl.windowsupdate.com -------------------------------------------------------------------------------- /data/rules/whitelist/ip.txt: -------------------------------------------------------------------------------- 1 | # You can add whitelisted IPs here. 2 | -------------------------------------------------------------------------------- /data/rules/whitelist/mispdomain.txt: -------------------------------------------------------------------------------- 1 | # Domains that should not be reported to MISP should be added here 2 | www.msftncsi.com 3 | dns.msftncsi.com 4 | teredo.ipv6.microsoft.com 5 | time.windows.com 6 | www.msftconnecttest.com 7 | v10.vortex-win.data.microsoft.com 8 | settings-win.data.microsoft.com 9 | win10.ipv6.microsoft.com 10 | sls.update.microsoft.com 11 | fs.microsoft.com 12 | ctldl.windowsupdate.com 13 | -------------------------------------------------------------------------------- /data/rules/whitelist/misphash.txt: -------------------------------------------------------------------------------- 1 | # Hashes of file that should not be reported to MISP should be added here -------------------------------------------------------------------------------- /data/rules/whitelist/mispip.txt: -------------------------------------------------------------------------------- 1 | # IPs that should not be reported to MISP should be added here 2 | 13.74.179.117 3 | 40.81.120.221 4 | 40.77.226.249 5 | 8.8.8.8 6 | -------------------------------------------------------------------------------- /data/rules/whitelist/mispurl.txt: -------------------------------------------------------------------------------- 1 | # URLs that should not be reported to MISP should be added here -------------------------------------------------------------------------------- /data/rules/yara/binaries/embedded.yar: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2010-2014 Cuckoo Foundation. 2 | // This file is part of Cuckoo Sandbox - http://www.cuckoosandbox.org 3 | // See the file 'docs/LICENSE' for copying permission. 4 | 5 | rule embedded_macho 6 | { 7 | meta: 8 | author = "nex" 9 | description = "Contains an embedded Mach-O file" 10 | 11 | strings: 12 | $magic1 = { ca fe ba be } 13 | $magic2 = { ce fa ed fe } 14 | $magic3 = { fe ed fa ce } 15 | condition: 16 | any of ($magic*) and not ($magic1 at 0) and not ($magic2 at 0) and not ($magic3 at 0) 17 | } 18 | 19 | rule embedded_pe 20 | { 21 | meta: 22 | author = "nex" 23 | description = "Contains an embedded PE32 file" 24 | 25 | strings: 26 | $a = "PE32" 27 | $b = "This program" 28 | $mz = { 4d 5a } 29 | condition: 30 | ($a or $b) and not ($mz at 0) 31 | } 32 | 33 | rule embedded_win_api 34 | { 35 | meta: 36 | author = "nex" 37 | description = "A non-Windows executable contains win32 API functions names" 38 | 39 | strings: 40 | $mz = { 4d 5a } 41 | $api1 = "CreateFileA" 42 | $api2 = "GetProcAddress" 43 | $api3 = "LoadLibraryA" 44 | $api4 = "WinExec" 45 | $api5 = "GetSystemDirectoryA" 46 | $api6 = "WriteFile" 47 | $api7 = "ShellExecute" 48 | $api8 = "GetWindowsDirectory" 49 | $api9 = "URLDownloadToFile" 50 | $api10 = "IsBadReadPtr" 51 | $api11 = "IsBadWritePtr" 52 | $api12 = "SetFilePointer" 53 | $api13 = "GetTempPath" 54 | $api14 = "GetWindowsDirectory" 55 | condition: 56 | not ($mz at 0) and any of ($api*) 57 | } 58 | -------------------------------------------------------------------------------- /data/rules/yara/binaries/shellcodes.yar: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2010-2014 Cuckoo Foundation. 2 | // This file is part of Cuckoo Sandbox - http://www.cuckoosandbox.org 3 | // See the file 'docs/LICENSE' for copying permission. 4 | 5 | rule shellcode 6 | { 7 | meta: 8 | author = "nex" 9 | description = "Matched shellcode byte patterns" 10 | 11 | strings: 12 | $mz = { 4d 5a } 13 | $shell1 = { 64 8b 64 } 14 | $shell2 = { 64 a1 30 } 15 | $shell3 = { 64 8b 15 30 } 16 | $shell4 = { 64 8b 35 30 } 17 | $shell5 = { 55 8b ec 83 c4 } 18 | $shell6 = { 55 8b ec 81 ec } 19 | $shell7 = { 55 8b ec e8 } 20 | $shell8 = { 55 8b ec e9 } 21 | condition: 22 | not ($mz at 0) and 23 | any of ($shell*) 24 | } 25 | -------------------------------------------------------------------------------- /data/rules/yara/binaries/vmdetect.yar: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2010-2014 Cuckoo Foundation. 2 | // This file is part of Cuckoo Sandbox - http://www.cuckoosandbox.org 3 | // See the file 'docs/LICENSE' for copying permission. 4 | 5 | rule vmdetect 6 | { 7 | meta: 8 | author = "nex" 9 | description = "Possibly employs anti-virtualization techniques" 10 | 11 | strings: 12 | // Binary tricks 13 | $vmware = {56 4D 58 68} 14 | $virtualpc = {0F 3F 07 0B} 15 | $ssexy = {66 0F 70 ?? ?? 66 0F DB ?? ?? ?? ?? ?? 66 0F DB ?? ?? ?? ?? ?? 66 0F EF} 16 | $vmcheckdll = {45 C7 00 01} 17 | $redpill = {0F 01 0D 00 00 00 00 C3} 18 | 19 | // Random strings 20 | $vmware1 = "VMXh" 21 | $vmware2 = "Ven_VMware_" nocase 22 | $vmware3 = "Prod_VMware_Virtual_" nocase 23 | $vmware4 = "hgfs.sys" nocase 24 | $vmware5 = "mhgfs.sys" nocase 25 | $vmware6 = "prleth.sys" nocase 26 | $vmware7 = "prlfs.sys" nocase 27 | $vmware8 = "prlmouse.sys" nocase 28 | $vmware9 = "prlvideo.sys" nocase 29 | $vmware10 = "prl_pv32.sys" nocase 30 | $vmware11 = "vpc-s3.sys" nocase 31 | $vmware12 = "vmsrvc.sys" nocase 32 | $vmware13 = "vmx86.sys" nocase 33 | $vmware14 = "vmnet.sys" nocase 34 | $vmware15 = "vmicheartbeat" nocase 35 | $vmware16 = "vmicvss" nocase 36 | $vmware17 = "vmicshutdown" nocase 37 | $vmware18 = "vmicexchange" nocase 38 | $vmware19 = "vmdebug" nocase 39 | $vmware20 = "vmmouse" nocase 40 | $vmware21 = "vmtools" nocase 41 | $vmware22 = "VMMEMCTL" nocase 42 | $vmware23 = "vmx86" nocase 43 | $vmware24 = "vmware" nocase 44 | $virtualpc1 = "vpcbus" nocase 45 | $virtualpc2 = "vpc-s3" nocase 46 | $virtualpc3 = "vpcuhub" nocase 47 | $virtualpc4 = "msvmmouf" nocase 48 | $xen1 = "xenevtchn" nocase 49 | $xen2 = "xennet" nocase 50 | $xen3 = "xennet6" nocase 51 | $xen4 = "xensvc" nocase 52 | $xen5 = "xenvdb" nocase 53 | $xen6 = "XenVMM" nocase 54 | $virtualbox1 = "VBoxHook.dll" nocase 55 | $virtualbox2 = "VBoxService" nocase 56 | $virtualbox3 = "VBoxTray" nocase 57 | $virtualbox4 = "VBoxMouse" nocase 58 | $virtualbox5 = "VBoxGuest" nocase 59 | $virtualbox6 = "VBoxSF" nocase 60 | $virtualbox7 = "VBoxGuestAdditions" nocase 61 | $virtualbox8 = "VBOX HARDDISK" nocase 62 | 63 | // MAC addresses 64 | $vmware_mac_1a = "00-05-69" 65 | $vmware_mac_1b = "00:05:69" 66 | $vmware_mac_1c = "000569" 67 | $vmware_mac_2a = "00-50-56" 68 | $vmware_mac_2b = "00:50:56" 69 | $vmware_mac_2c = "005056" 70 | $vmware_mac_3a = "00-0C-29" nocase 71 | $vmware_mac_3b = "00:0C:29" nocase 72 | $vmware_mac_3c = "000C29" nocase 73 | $vmware_mac_4a = "00-1C-14" nocase 74 | $vmware_mac_4b = "00:1C:14" nocase 75 | $vmware_mac_4c = "001C14" nocase 76 | $virtualbox_mac_1a = "08-00-27" 77 | $virtualbox_mac_1b = "08:00:27" 78 | $virtualbox_mac_1c = "080027" 79 | 80 | condition: 81 | any of them 82 | } 83 | -------------------------------------------------------------------------------- /data/rules/yara/dumpmem/readme_first.txt: -------------------------------------------------------------------------------- 1 | This directory identifies the "dumpmem" Yara rules. These Yara rules are used 2 | by zer0m0n, *INSIDE* the Virtual Machine (!), during an analysis to perform 3 | real-time process memory scanning. 4 | 5 | This approach features one major advantages and one major disadvantages which 6 | should be made clear before usage by any user. 7 | 8 | Disadvantage: rules find their way into the VM 9 | 10 | Rules present in this directory are uploaded to the VM (in a compiled 11 | fashion) and are therefore exposed to the artefacts executed inside the 12 | VM, rendering it possible that they're exfiltrated at some point. 13 | 14 | Advantage: major performance improvement 15 | 16 | By performing real-time process memory scanning with Yara from within the 17 | VM using zer0m0n it's possible to perform many hundreds of scans during an 18 | analysis while only uploading a process memory dump in case a Yara rule 19 | has actually triggered - which, as one may imagine, greatly improves and 20 | speeds up the process of finding relevant process memory dumps (keeping 21 | into account that, depending on hardware, just a single 50MB process 22 | memory dump may take an entire second to write to harddisk). 23 | 24 | Combining the pros and cons one may decide to pursue usage of the "dumpmem" 25 | Yara rules, but rather than including entire Yara rules, only include the bare 26 | minimum required for proper identification of potential artefacts. 27 | -------------------------------------------------------------------------------- /data/rules/yara/memory/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryuchen/Panda-Sandbox/09ae5da4b0ee4c688311208ce819dae82593490a/data/rules/yara/memory/.gitignore -------------------------------------------------------------------------------- /data/rules/yara/office/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryuchen/Panda-Sandbox/09ae5da4b0ee4c688311208ce819dae82593490a/data/rules/yara/office/.gitignore -------------------------------------------------------------------------------- /data/rules/yara/scripts/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryuchen/Panda-Sandbox/09ae5da4b0ee4c688311208ce819dae82593490a/data/rules/yara/scripts/.gitignore -------------------------------------------------------------------------------- /data/rules/yara/shellcode/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryuchen/Panda-Sandbox/09ae5da4b0ee4c688311208ce819dae82593490a/data/rules/yara/shellcode/.gitignore -------------------------------------------------------------------------------- /data/rules/yara/urls/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryuchen/Panda-Sandbox/09ae5da4b0ee4c688311208ce819dae82593490a/data/rules/yara/urls/.gitignore -------------------------------------------------------------------------------- /docs/HTML/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryuchen/Panda-Sandbox/09ae5da4b0ee4c688311208ce819dae82593490a/docs/HTML/.gitkeep -------------------------------------------------------------------------------- /docs/MD/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryuchen/Panda-Sandbox/09ae5da4b0ee4c688311208ce819dae82593490a/docs/MD/.gitkeep -------------------------------------------------------------------------------- /docs/PDF/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryuchen/Panda-Sandbox/09ae5da4b0ee4c688311208ce819dae82593490a/docs/PDF/.gitkeep -------------------------------------------------------------------------------- /docs/RST/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryuchen/Panda-Sandbox/09ae5da4b0ee4c688311208ce819dae82593490a/docs/RST/.gitkeep -------------------------------------------------------------------------------- /docs/sponsor/alipay.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryuchen/Panda-Sandbox/09ae5da4b0ee4c688311208ce819dae82593490a/docs/sponsor/alipay.jpg -------------------------------------------------------------------------------- /docs/sponsor/wechat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryuchen/Panda-Sandbox/09ae5da4b0ee4c688311208ce819dae82593490a/docs/sponsor/wechat.jpg -------------------------------------------------------------------------------- /etc/Panda.service: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryuchen/Panda-Sandbox/09ae5da4b0ee4c688311208ce819dae82593490a/etc/Panda.service -------------------------------------------------------------------------------- /etc/conf.d/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryuchen/Panda-Sandbox/09ae5da4b0ee4c688311208ce819dae82593490a/etc/conf.d/.gitkeep -------------------------------------------------------------------------------- /etc/conf.d/custom.yaml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryuchen/Panda-Sandbox/09ae5da4b0ee4c688311208ce819dae82593490a/etc/conf.d/custom.yaml -------------------------------------------------------------------------------- /etc/default.yaml: -------------------------------------------------------------------------------- 1 | ########################################################### 2 | # Panda Sandbox(钟馗沙箱) # 3 | ########################################################### 4 | # 5 | # Sandbox Release Version 6 | version: 1.0.0-alpha 7 | 8 | # Panda Variable Setting 9 | variable: 10 | # The current host localname 11 | hostname: default 12 | # The current host manage NIC address 13 | hostaddr: "192.168.0.1" 14 | 15 | # Panda Advanced Setting 16 | advanced: 17 | ## The running mode of the sandbox to filter file 18 | ## Single mode: 1 19 | ## Cluster mode: 2 20 | mode: 1 21 | #################### Cluster Mode ################### 22 | master: yes 23 | server: 172.16.12.1 24 | ## Enable creation of memory dump of the analysis machine before shutting 25 | ## down. Even if turned off, this functionality can also be enabled at 26 | ## submission. Currently available for: VirtualBox and Libvirt modules (KVM). 27 | memory_dump: False 28 | ## To enable the procmon analyser running in the machine. 29 | procmon_dump: False 30 | ## The file operations by the malware in the machine. 31 | dropped_files: True 32 | ## When the timeout of an analysis is hit, the VM is just killed by default. 33 | ## For some long-running setups it might be interesting to terminate the 34 | ## monitored processes before killing the VM so that connections are closed. 35 | terminate_processes: False 36 | -------------------------------------------------------------------------------- /etc/example.yaml: -------------------------------------------------------------------------------- 1 | ########################################################### 2 | # Panda Sandbox(钟馗沙箱) # 3 | ########################################################### 4 | # 5 | # Sandbox Release Version 6 | version: 1.0.0-alpha 7 | 8 | # Environment Global Variable 9 | variable: 10 | # The current host localname 11 | hostname: "default" 12 | # The current host manage NIC address 13 | hostaddr: "192.168.0.1" 14 | 15 | # Sandbox Mode Setting 16 | settings: 17 | ## The running mode of the sandbox to filter file 18 | ## Single mode: 1 19 | ## Cluster mode: 2 20 | mode: 1 21 | #################### Cluster Mode ################### 22 | master: False 23 | server: 172.16.12.1 24 | ## Enable creation of memory dump of the analysis machine before shutting 25 | ## down. Even if turned off, this functionality can also be enabled at 26 | ## submission. Currently available for: VirtualBox and Libvirt modules (KVM). 27 | memory_dump: False 28 | ## To enable the procmon analyser running in the machine. 29 | procmon_dump: False 30 | ## The file operations by the malware in the machine. 31 | dropped_files: True 32 | ## When the timeout of an analysis is hit, the VM is just killed by default. 33 | ## For some long-running setups it might be interesting to terminate the 34 | ## monitored processes before killing the VM so that connections are closed. 35 | terminate_processes: False 36 | 37 | ## The platform of file support by sandbox, inspector depends on this to determine whether 38 | ## to store this task in sandbox workflow, by default if it turn on the task will put into 39 | ## queue in workflow, with no consumer it will block the inspector thread. 40 | support: 41 | linux: 42 | enable: False 43 | extension: 44 | - 'shell' 45 | - 'elf' 46 | - 'deb' 47 | - 'rpm' 48 | - 'kernel' 49 | - 'perl' 50 | darwin: 51 | enable: False 52 | extension: 53 | - 'mac' 54 | android: 55 | enable: False 56 | extension: 57 | - 'apk' 58 | - 'dex' 59 | archive: 60 | enable: True 61 | extension: 62 | - '7z' 63 | - 'lzh' 64 | - 'rar' 65 | - 'zip' 66 | - 'eml' 67 | - 'msg' 68 | - 'ace' 69 | - 'cab' 70 | - 'bzip' 71 | - 'gzip' 72 | browser: 73 | enable: True 74 | extension: 75 | - 'crx' 76 | - 'htm' 77 | - 'txt' 78 | windows: 79 | enable: True 80 | extension: 81 | - 'js' 82 | - 'exe' 83 | - 'doc' 84 | - 'xls' 85 | - 'ppt' 86 | - 'pdf' 87 | - 'msi' 88 | - 'rtf' 89 | - 'ps1' 90 | - 'inf' 91 | - 'zip' 92 | - 'tar' 93 | - 'jar' 94 | - 'dll' 95 | - 'bin' 96 | - 'lnk' 97 | - 'bat' 98 | - 'cmd' 99 | - 'com' 100 | - 'cpl' 101 | - 'dos' 102 | - 'emf' 103 | - 'gif' 104 | - 'hlp' 105 | - 'img' 106 | - 'jpg' 107 | - 'png' 108 | - 'psi' 109 | - 'swf' 110 | - 'sys' 111 | - 'vbs' 112 | - 'wps' 113 | - 'wsf' 114 | - 'xar' 115 | - 'xml' 116 | - 'pptx' 117 | - 'xlsx' 118 | - 'docx' 119 | - 'exe+' 120 | - 'pcap' 121 | - 'audio' 122 | - 'video' 123 | - 'python' 124 | - 'generic' 125 | 126 | 127 | # Sandbox Logging Settings 128 | logging: 129 | version: 1 130 | disable_existing_loggers: False 131 | formatters: 132 | simple: 133 | format: "%(asctime)s | %(levelname)s | %(message)s" 134 | detail: 135 | format: "%(asctime)s-[%(filename)s:%(lineno)s-%(levelname)s]: %(message)s" 136 | handlers: 137 | console: 138 | class: "lib.panda.plugins.logger.ConsoleHandler.ConsoleHandler" 139 | level: DEBUG 140 | formatter: detail 141 | stream: ext://sys.stdout 142 | sandbox: 143 | class: logging.FileHandler 144 | level: INFO 145 | formatter: detail 146 | filename: "/data1/logs/cluster/panda.log" 147 | backend: 148 | class: logging.FileHandler 149 | level: DEBUG 150 | formatter: detail 151 | filename: "/data1/logs/cluster/backend.log" 152 | libvirt: 153 | class: logging.FileHandler 154 | level: ERROR 155 | formatter: detail 156 | filename: "/data1/logs/cluster/libvirt.log" 157 | distribute: 158 | class: logging.FileHandler 159 | level: DEBUG 160 | formatter: detail 161 | filename: "/data1/logs/cluster/distribute.log" 162 | loggers: 163 | sandbox: 164 | level: INFO 165 | handlers: [sandbox] 166 | propagate: False 167 | backend: 168 | level: INFO 169 | handlers: [backend] 170 | propagate: False 171 | libvirt: 172 | level: ERROR 173 | handlers: [libvirt] 174 | propagate: False 175 | distribute: 176 | level: DEBUG 177 | handlers: [distribute] 178 | propagate: False 179 | roots: 180 | level: ERROR 181 | handlers: [] 182 | -------------------------------------------------------------------------------- /etc/plugin.d/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryuchen/Panda-Sandbox/09ae5da4b0ee4c688311208ce819dae82593490a/etc/plugin.d/.gitkeep -------------------------------------------------------------------------------- /lib/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # ================================================== 4 | # @Time : 2019-06-20 10:23 5 | # @Author : ryuchen 6 | # @File : __init__.py.py 7 | # @Desc : 8 | # ================================================== -------------------------------------------------------------------------------- /lib/base/Application.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # ================================================== 4 | # @Time : 2019-06-20 14:33 5 | # @Author : ryuchen 6 | # @File : Application.py 7 | # @Desc : 8 | # ================================================== 9 | 10 | from multiprocessing import Process 11 | 12 | 13 | class Application(Process): 14 | 15 | def __init__(self): 16 | super(Application, self).__init__() 17 | -------------------------------------------------------------------------------- /lib/base/Machinery.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # ======================================================== 4 | # @Author: Ryuchen 5 | # @Time: 2019/12/10-21:26 6 | # @Site: https://ryuchen.github.io 7 | # @Contact: chenhaom1993@hotmail.com 8 | # @Copyright: Copyright (C) 2019-2020 Panda-Sandbox. 9 | # ======================================================== 10 | """ 11 | This function will be the basic guest machinery modules. 12 | 13 | It will be fixed with virtualbox by default, other virtualization platform should implement this. 14 | 15 | In our machine module, we wish it can manage itself, so it must have three main function here: 16 | 17 | 1. Each machine must have its own options, according to its options, it will fetch specification file type; 18 | 2. Each machine will be a pipeline, it will fetch & analysis & revert by itself automatically; 19 | 3. Each machine has its own watch dog, to take care of the healthy situation of machine, 20 | in some situation, watch dog will help rebuild the machine image. 21 | 22 | Also, we will add some additional function to help us analysis file more efficiency. 23 | """ 24 | import time 25 | import logging 26 | 27 | log = logging.getLogger(__name__) 28 | 29 | 30 | class Machinery(object): 31 | """Base abstract class for machinery modules.""" 32 | 33 | # UUID used at here to identify each machine 34 | UUID = "" 35 | 36 | def __init__(self): 37 | self.options = None 38 | self.remote_control = False 39 | 40 | @classmethod 41 | def init_once(cls): 42 | pass 43 | 44 | def pcap_path(self, task_id): 45 | """Return the .pcap path for this task id.""" 46 | return cwd("storage", "analyses", "%s" % task_id, "dump.pcap") 47 | 48 | def set_options(self, options): 49 | """Set machine manager options. 50 | @param options: machine manager options dict. 51 | """ 52 | self.options = options 53 | 54 | def initialize(self, module_name): 55 | """Read, load, and verify machines configuration. 56 | @param module_name: module name. 57 | """ 58 | # Load. 59 | self._initialize(module_name) 60 | 61 | # Run initialization checks. 62 | self._initialize_check() 63 | 64 | def _initialize(self, module_name): 65 | """Read configuration. 66 | @param module_name: module name. 67 | """ 68 | machinery = self.options.get(module_name) 69 | for vmname in machinery["machines"]: 70 | options = self.options.get(vmname) 71 | 72 | # If configured, use specific network interface for this 73 | # machine, else use the default value. 74 | if options.get("interface"): 75 | interface = options["interface"] 76 | else: 77 | interface = machinery.get("interface") 78 | 79 | if options.get("resultserver_ip"): 80 | ip = options["resultserver_ip"] 81 | else: 82 | ip = config("cuckoo:resultserver:ip") 83 | 84 | if options.get("resultserver_port"): 85 | port = options["resultserver_port"] 86 | else: 87 | # The ResultServer port might have been dynamically changed, 88 | # get it from the ResultServer singleton. Also avoid import 89 | # recursion issues by importing ResultServer here. 90 | from cuckoo.core.resultserver import ResultServer 91 | port = ResultServer().port 92 | 93 | self.db.add_machine( 94 | name=vmname, 95 | label=options[self.LABEL], 96 | ip=options.ip, 97 | platform=options.platform, 98 | options=options.get("options", ""), 99 | tags=options.tags, 100 | interface=interface, 101 | snapshot=options.snapshot, 102 | resultserver_ip=ip, 103 | resultserver_port=port 104 | ) 105 | 106 | def _initialize_check(self): 107 | """Run checks against virtualization software when a machine manager 108 | is initialized. 109 | @note: in machine manager modules you may override or superclass 110 | his method. 111 | @raise CuckooMachineError: if a misconfiguration or a unkown vm state 112 | is found. 113 | """ 114 | try: 115 | configured_vms = self._list() 116 | except NotImplementedError: 117 | return 118 | 119 | for machine in self.machines(): 120 | # If this machine is already in the "correct" state, then we 121 | # go on to the next machine. 122 | if machine.label in configured_vms and \ 123 | self._status(machine.label) in [self.POWEROFF, self.ABORTED]: 124 | continue 125 | 126 | # This machine is currently not in its correct state, we're going 127 | # to try to shut it down. If that works, then the machine is fine. 128 | try: 129 | self.stop(machine.label) 130 | except CuckooMachineError as e: 131 | raise CuckooCriticalError( 132 | "Please update your configuration. Unable to shut '%s' " 133 | "down or find the machine in its proper state: %s" % 134 | (machine.label, e) 135 | ) 136 | 137 | if not config("cuckoo:timeouts:vm_state"): 138 | raise CuckooCriticalError( 139 | "Virtual machine state change timeout has not been set " 140 | "properly, please update it to be non-null." 141 | ) 142 | 143 | def machines(self): 144 | """List virtual machines. 145 | @return: virtual machines list 146 | """ 147 | return self.db.list_machines() 148 | 149 | def availables(self): 150 | """Return how many machines are free. 151 | @return: free machines count. 152 | """ 153 | return self.db.count_machines_available() 154 | 155 | def acquire(self, machine_id=None, platform=None, tags=None): 156 | """Acquire a machine to start analysis. 157 | @param machine_id: machine ID. 158 | @param platform: machine platform. 159 | @param tags: machine tags 160 | @return: machine or None. 161 | """ 162 | if machine_id: 163 | return self.db.lock_machine(label=machine_id) 164 | elif platform: 165 | return self.db.lock_machine(platform=platform, tags=tags) 166 | else: 167 | return self.db.lock_machine(tags=tags) 168 | 169 | def release(self, label=None): 170 | """Release a machine. 171 | @param label: machine name. 172 | """ 173 | self.db.unlock_machine(label) 174 | 175 | def running(self): 176 | """Return running virtual machines. 177 | @return: running virtual machines list. 178 | """ 179 | return self.db.list_machines(locked=True) 180 | 181 | def shutdown(self): 182 | """Shutdown the machine manager and kill all alive machines. 183 | @raise CuckooMachineError: if unable to stop machine. 184 | """ 185 | if len(self.running()) > 0: 186 | log.info("Still %s guests alive. Shutting down...", 187 | len(self.running())) 188 | for machine in self.running(): 189 | try: 190 | self.stop(machine.label) 191 | except CuckooMachineError as e: 192 | log.warning("Unable to shutdown machine %s, please check " 193 | "manually. Error: %s", machine.label, e) 194 | 195 | def set_status(self, label, status): 196 | """Set status for a virtual machine. 197 | @param label: virtual machine label 198 | @param status: new virtual machine status 199 | """ 200 | self.db.set_machine_status(label, status) 201 | 202 | def start(self, label, task): 203 | """Start a machine. 204 | @param label: machine name. 205 | @param task: task object. 206 | @raise NotImplementedError: this method is abstract. 207 | """ 208 | raise NotImplementedError 209 | 210 | def stop(self, label=None): 211 | """Stop a machine. 212 | @param label: machine name. 213 | @raise NotImplementedError: this method is abstract. 214 | """ 215 | raise NotImplementedError 216 | 217 | def _list(self): 218 | """List virtual machines configured. 219 | @raise NotImplementedError: this method is abstract. 220 | """ 221 | raise NotImplementedError 222 | 223 | def dump_memory(self, label, path): 224 | """Take a memory dump of a machine. 225 | @param path: path to where to store the memory dump. 226 | """ 227 | raise NotImplementedError 228 | 229 | def enable_remote_control(self, label): 230 | """Enable remote control interface (RDP/VNC/SSH). 231 | @param label: machine name. 232 | @return: None 233 | """ 234 | raise NotImplementedError 235 | 236 | def disable_remote_control(self, label): 237 | """Disable remote control interface (RDP/VNC/SSH). 238 | @param label: machine name. 239 | @return: None 240 | """ 241 | raise NotImplementedError 242 | 243 | def get_remote_control_params(self, label): 244 | """Return connection details for remote control. 245 | @param label: machine name. 246 | @return: dict with keys: protocol, host, port 247 | """ 248 | raise NotImplementedError 249 | 250 | def _wait_status(self, label, *states): 251 | """Wait for a vm status. 252 | @param label: virtual machine name. 253 | @param state: virtual machine status, accepts multiple states as list. 254 | @raise CuckooMachineError: if default waiting timeout expire. 255 | """ 256 | # This block was originally suggested by Loic Jaquemet. 257 | waitme = 0 258 | try: 259 | current = self._status(label) 260 | except NameError: 261 | return 262 | 263 | while current not in states: 264 | log.debug("Waiting %i cuckooseconds for machine %s to switch " 265 | "to status %s", waitme, label, states) 266 | if waitme > config("cuckoo:timeouts:vm_state"): 267 | raise CuckooMachineError( 268 | "Timeout hit while for machine %s to change status" % label 269 | ) 270 | 271 | time.sleep(1) 272 | waitme += 1 273 | current = self._status(label) 274 | 275 | @staticmethod 276 | def version(): 277 | """Return the version of the virtualization software""" 278 | return None 279 | -------------------------------------------------------------------------------- /lib/base/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # ================================================== 4 | # @Time : 2019-06-20 11:04 5 | # @Author : ryuchen 6 | # @File : __init__.py.py 7 | # @Desc : 8 | # ================================================== -------------------------------------------------------------------------------- /lib/common/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # ================================================== 4 | # @Time : 2019-05-27 23:51 5 | # @Author : ryuchen 6 | # @Site : 7 | # @File : __init__.py.py 8 | # @Desc : 9 | # ================================================== -------------------------------------------------------------------------------- /lib/common/config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # ================================================== 4 | # @Time : 2019-06-20 22:31 5 | # @Author : ryuchen 6 | # @File : config.py 7 | # @Desc : 8 | # ================================================== 9 | import os 10 | import logging 11 | import configparser 12 | 13 | from lib.defines.types import Int, Boolean, String, Path, List 14 | from lib.exceptions.critical import CuckooConfigurationError 15 | from cuckoo.misc import cwd 16 | 17 | log = logging.getLogger(__name__) 18 | 19 | _cache = {} 20 | 21 | 22 | class Config(object): 23 | """Configuration file parser.""" 24 | 25 | configuration = { 26 | "cuckoo": { 27 | "cuckoo": { 28 | "version_check": Boolean(True), 29 | "ignore_vulnerabilities": Boolean(False, required=False), 30 | "delete_original": Boolean(False), 31 | "delete_bin_copy": Boolean(False), 32 | "machinery": String("virtualbox"), 33 | "memory_dump": Boolean(False), 34 | "terminate_processes": Boolean(False), 35 | "reschedule": Boolean(False), 36 | "process_results": Boolean(True), 37 | "max_analysis_count": Int(0), 38 | "max_machines_count": Int(0), 39 | "max_vmstartup_count": Int(10), 40 | "freespace": Int(1024), 41 | "tmppath": Path( 42 | exists=True, writable=True, readable=False, allow_empty=True 43 | ), 44 | "api_token": String(allow_empty=True, sanitize=True, required=False), 45 | "web_secret": String(allow_empty=True, sanitize=True, required=False), 46 | "rooter": Path( 47 | "/tmp/cuckoo-rooter", exists=False, writable=False, readable=False 48 | ), 49 | }, 50 | "feedback": { 51 | "enabled": Boolean(False), 52 | "name": String(), 53 | "company": String(), 54 | "email": String(), 55 | }, 56 | "resultserver": { 57 | "ip": String("192.168.56.1"), 58 | "port": Int(2042), 59 | "force_port": Boolean(False, False), # Unused 60 | "pool_size": Int(0, False), 61 | "upload_max_size": Int(128 * 1024 * 1024), 62 | }, 63 | "processing": { 64 | "analysis_size_limit": Int(128 * 1024 * 1024), 65 | "resolve_dns": Boolean(True), 66 | "sort_pcap": Boolean(True), 67 | }, 68 | "database": { 69 | "connection": String(sanitize=True), 70 | "timeout": Int(60, allow_empty=True), 71 | }, 72 | "timeouts": {"default": Int(120), "critical": Int(60), "vm_state": Int(60)}, 73 | "remotecontrol": { 74 | "enabled": Boolean(False), 75 | "guacd_host": String("localhost"), 76 | "guacd_port": Int(4822), 77 | }, 78 | }, 79 | "virtualbox": { 80 | "virtualbox": { 81 | "mode": String("headless"), 82 | "path": Path( 83 | "/usr/bin/VBoxManage", exists=False, writable=False, readable=True 84 | ), 85 | "interface": String("vboxnet0"), 86 | "machines": List(String, "cuckoo1"), 87 | "controlports": String("5000-5050", required=False), 88 | }, 89 | "*": { 90 | "__section__": "cuckoo1", 91 | "label": String("cuckoo1"), 92 | "platform": String("windows"), 93 | "ip": String("192.168.56.101"), 94 | "snapshot": String(), 95 | "interface": String(), 96 | "resultserver_ip": String(), 97 | "resultserver_port": Int(), 98 | "tags": String(), 99 | "options": List(String, None, ",\\s"), 100 | "osprofile": String(required=False), 101 | }, 102 | "__star__": ("virtualbox", "machines"), 103 | }, 104 | "auxiliary": { 105 | "sniffer": { 106 | "enabled": Boolean(True), 107 | "tcpdump": Path( 108 | "/usr/sbin/tcpdump", exists=False, writable=False, readable=True 109 | ), 110 | "bpf": String(), 111 | }, 112 | "mitm": { 113 | "enabled": Boolean(False), 114 | "mitmdump": Path( 115 | "/usr/local/bin/mitmdump", 116 | exists=False, 117 | writable=False, 118 | readable=True, 119 | ), 120 | "port_base": Int(50000), 121 | "script": Path( 122 | "stuff/mitm.py", exists=False, writable=False, readable=True 123 | ), 124 | "certificate": Path( 125 | "bin/cert.p12", exists=False, writable=False, readable=True 126 | ), 127 | }, 128 | "replay": { 129 | "enabled": Boolean(True, required=False), 130 | "mitmdump": Path( 131 | "/usr/local/bin/mitmdump", 132 | exists=False, 133 | writable=False, 134 | readable=True, 135 | required=False, 136 | ), 137 | "port_base": Int(51000, required=False), 138 | "certificate": Path( 139 | "bin/cert.p12", 140 | exists=False, 141 | writable=False, 142 | readable=True, 143 | required=False, 144 | ), 145 | }, 146 | "services": { 147 | "enabled": Boolean(False), 148 | "services": String("honeyd"), 149 | "timeout": Int(0), 150 | }, 151 | "reboot": {"enabled": Boolean(True)}, 152 | }, 153 | "avd": { 154 | "avd": { 155 | "mode": String("headless"), 156 | "emulator_path": Path( 157 | "/home/cuckoo/android-sdk-linux/tools/emulator", 158 | exists=True, 159 | writable=False, 160 | readable=True, 161 | ), 162 | "adb_path": Path( 163 | "/home/cuckoo/android-sdk-linux/platform-tools/adb", 164 | exists=True, 165 | writable=False, 166 | readable=True, 167 | ), 168 | "avd_path": Path( 169 | "/home/cuckoo/.android/avd", 170 | exists=True, 171 | writable=False, 172 | readable=True, 173 | ), 174 | "reference_machine": String("cuckoo-bird"), 175 | "machines": List(String, "cuckoo1"), 176 | }, 177 | "*": { 178 | "__section__": "cuckoo1", 179 | "label": String("cuckoo1"), 180 | "platform": String("android"), 181 | "ip": String("127.0.0.1"), 182 | "emulator_port": Int(5554), 183 | "resultserver_ip": String("10.0.2.2"), 184 | "resultserver_port": Int(2042), 185 | "osprofile": String(required=False), 186 | }, 187 | "__star__": ("avd", "machines"), 188 | }, 189 | "memory": { 190 | "basic": { 191 | "guest_profile": String("WinXPSP2x86"), 192 | "delete_memdump": Boolean(False), 193 | }, 194 | "malfind": {"enabled": Boolean(True), "filter": Boolean(True)}, 195 | "apihooks": {"enabled": Boolean(False), "filter": Boolean(True)}, 196 | "pslist": {"enabled": Boolean(True), "filter": Boolean(False)}, 197 | "psxview": {"enabled": Boolean(True), "filter": Boolean(False)}, 198 | "callbacks": {"enabled": Boolean(True), "filter": Boolean(False)}, 199 | "idt": {"enabled": Boolean(True), "filter": Boolean(False)}, 200 | "timers": {"enabled": Boolean(True), "filter": Boolean(False)}, 201 | "messagehooks": {"enabled": Boolean(False), "filter": Boolean(False)}, 202 | "getsids": {"enabled": Boolean(True), "filter": Boolean(False)}, 203 | "privs": {"enabled": Boolean(True), "filter": Boolean(False)}, 204 | "dlllist": {"enabled": Boolean(True), "filter": Boolean(True)}, 205 | "handles": {"enabled": Boolean(True), "filter": Boolean(True)}, 206 | "ldrmodules": {"enabled": Boolean(True), "filter": Boolean(True)}, 207 | "mutantscan": {"enabled": Boolean(True), "filter": Boolean(True)}, 208 | "devicetree": {"enabled": Boolean(True), "filter": Boolean(True)}, 209 | "svcscan": {"enabled": Boolean(True), "filter": Boolean(True)}, 210 | "modscan": {"enabled": Boolean(True), "filter": Boolean(True)}, 211 | "yarascan": {"enabled": Boolean(True), "filter": Boolean(True)}, 212 | "ssdt": {"enabled": Boolean(True), "filter": Boolean(True)}, 213 | "gdt": {"enabled": Boolean(True), "filter": Boolean(True)}, 214 | "sockscan": {"enabled": Boolean(True), "filter": Boolean(False)}, 215 | "netscan": {"enabled": Boolean(True), "filter": Boolean(False)}, 216 | "mask": {"enabled": Boolean(False), "pid_generic": List(String, None)}, 217 | }, 218 | "processing": { 219 | "analysisinfo": {"enabled": Boolean(True)}, 220 | "apkinfo": { 221 | "enabled": Boolean(False), 222 | "decompilation_threshold": Int(5000000), 223 | }, 224 | "baseline": {"enabled": Boolean(False)}, 225 | "behavior": {"enabled": Boolean(True)}, 226 | "buffer": {"enabled": Boolean(True)}, 227 | "debug": {"enabled": Boolean(True)}, 228 | "droidmon": {"enabled": Boolean(False)}, 229 | "dropped": {"enabled": Boolean(True)}, 230 | "dumptls": {"enabled": Boolean(True)}, 231 | "extracted": {"enabled": Boolean(True, required=False)}, 232 | "googleplay": { 233 | "enabled": Boolean(False), 234 | "android_id": String(), 235 | "google_login": String(), 236 | "google_password": String(sanitize=True), 237 | }, 238 | "memory": {"enabled": Boolean(False)}, 239 | "misp": { 240 | "enabled": Boolean(False), 241 | "url": String(), 242 | "apikey": String(sanitize=True), 243 | "maxioc": Int(100), 244 | }, 245 | "network": { 246 | "enabled": Boolean(True), 247 | "whitelist_dns": Boolean(False), 248 | "allowed_dns": String(), 249 | }, 250 | "procmemory": { 251 | "enabled": Boolean(True), 252 | "idapro": Boolean(False), 253 | "extract_img": Boolean(True), 254 | "extract_dll": Boolean(False), 255 | "dump_delete": Boolean(False), 256 | }, 257 | "procmon": {"enabled": Boolean(True)}, 258 | "screenshots": {"enabled": Boolean(True), "tesseract": String("no")}, 259 | "snort": { 260 | "enabled": Boolean(False), 261 | "snort": Path( 262 | "/usr/local/bin/snort", exists=False, writable=False, readable=True 263 | ), 264 | "conf": Path( 265 | "/etc/snort/snort.conf", exists=False, writable=False, readable=True 266 | ), 267 | }, 268 | "static": {"enabled": Boolean(True), "pdf_timeout": Int(60)}, 269 | "strings": {"enabled": Boolean(True)}, 270 | "suricata": { 271 | "enabled": Boolean(False), 272 | "suricata": Path( 273 | "/usr/bin/suricata", exists=True, writable=False, readable=True 274 | ), 275 | "conf": Path( 276 | "/etc/suricata/suricata.yaml", 277 | exists=True, 278 | writable=False, 279 | readable=True, 280 | ), 281 | "eve_log": Path( 282 | "eve.json", exists=False, writable=True, readable=False 283 | ), 284 | "files_log": Path( 285 | "files-json.log", exists=False, writable=True, readable=False 286 | ), 287 | "files_dir": Path("files", exists=False, writable=False, readable=True), 288 | "socket": Path( 289 | exists=True, writable=False, readable=True, allow_empty=True 290 | ), 291 | }, 292 | "targetinfo": {"enabled": Boolean(True)}, 293 | "virustotal": { 294 | "enabled": Boolean(False), 295 | "timeout": Int(60), 296 | "scan": Boolean(False), 297 | "key": String( 298 | "a0283a2c3d55728300d064874239b5346fb991317e8449fe43c902879d758088", 299 | sanitize=True, 300 | ), 301 | }, 302 | "irma": { 303 | "enabled": Boolean(False), 304 | "timeout": Int(60), 305 | "scan": Boolean(False), 306 | "force": Boolean(False), 307 | "url": String(), 308 | "probes": String(required=False), 309 | }, 310 | }, 311 | "reporting": { 312 | "feedback": {"enabled": Boolean(False)}, 313 | "jsondump": { 314 | "enabled": Boolean(True), 315 | "indent": Int(4), 316 | "calls": Boolean(True), 317 | }, 318 | "singlefile": { 319 | "enabled": Boolean(False), 320 | "html": Boolean(False), 321 | "pdf": Boolean(False), 322 | }, 323 | "misp": { 324 | "enabled": Boolean(False), 325 | "url": String(), 326 | "apikey": String(sanitize=True), 327 | "mode": String("maldoc ipaddr hashes url"), 328 | "distribution": Int(0, required=False), 329 | "analysis": Int(0, required=False), 330 | "threat_level": Int(4, required=False), 331 | "min_malscore": Int(0, required=False), 332 | "tag": String("Cuckoo", required=False), 333 | "upload_sample": Boolean(False, required=False), 334 | }, 335 | "mongodb": { 336 | "enabled": Boolean(False), 337 | "host": String("127.0.0.1"), 338 | "port": Int(27017), 339 | "db": String("cuckoo"), 340 | "store_memdump": Boolean(True), 341 | "paginate": Int(100), 342 | "username": String(), 343 | "password": String(), 344 | }, 345 | "elasticsearch": { 346 | "enabled": Boolean(False), 347 | "hosts": List(String, "127.0.0.1"), 348 | "timeout": Int(300), 349 | "calls": Boolean(False), 350 | "index": String("cuckoo"), 351 | "index_time_pattern": String("yearly"), 352 | "cuckoo_node": String(), 353 | }, 354 | "moloch": { 355 | "enabled": Boolean(False), 356 | "host": String(), 357 | "insecure": Boolean(False), 358 | "moloch_capture": Path( 359 | "/data/moloch/bin/moloch-capture", 360 | exists=True, 361 | writable=False, 362 | readable=True, 363 | ), 364 | "conf": Path( 365 | "/data/moloch/etc/config.ini", 366 | exists=True, 367 | writable=False, 368 | readable=True, 369 | ), 370 | "instance": String("cuckoo"), 371 | }, 372 | "notification": { 373 | "enabled": Boolean(False), 374 | "url": String(), 375 | "identifier": String(), 376 | }, 377 | "mattermost": { 378 | "enabled": Boolean(False), 379 | "username": String("cuckoo"), 380 | "url": String(), 381 | "myurl": String(), 382 | "show_virustotal": Boolean(False), 383 | "show_signatures": Boolean(False), 384 | "show_urls": Boolean(False), 385 | "hash_filename": Boolean(False), 386 | "hash_url": Boolean(False), 387 | }, 388 | }, 389 | "routing": { 390 | "routing": { 391 | "route": String("none"), 392 | "internet": String("none"), 393 | "rt_table": String("main"), 394 | "auto_rt": Boolean(True), 395 | "drop": Boolean(False), 396 | }, 397 | "inetsim": { 398 | "enabled": Boolean(False), 399 | "server": String("192.168.56.1"), 400 | "ports": String(), 401 | }, 402 | "tor": { 403 | "enabled": Boolean(False), 404 | "dnsport": Int(5353), 405 | "proxyport": Int(9040), 406 | }, 407 | "vpn": {"enabled": Boolean(False), "vpns": List(String, "vpn0")}, 408 | "*": { 409 | "__section__": "vpn0", 410 | "name": String("vpn0"), 411 | "description": String("Spain, Europe"), 412 | "interface": String("tun0"), 413 | "rt_table": String("tun0"), 414 | }, 415 | "__star__": ("vpn", "vpns"), 416 | }, 417 | } 418 | 419 | @staticmethod 420 | def get_section_types(file_name, section, strict=False, loose=False): 421 | """Get types for a section entry.""" 422 | section_types = get_section_types(file_name, section) 423 | if not section_types and not loose: 424 | log.error("Config section %s:%s not found!", file_name, section) 425 | if strict: 426 | raise CuckooConfigurationError( 427 | "Config section %s:%s not found!", file_name, section 428 | ) 429 | return 430 | return section_types 431 | 432 | def __init__(self, file_name="cuckoo", cfg=None, strict=False, loose=False, raw=False): 433 | """ 434 | @param file_name: file name without extension. 435 | @param cfg: configuration file path. 436 | """ 437 | env = {} 438 | for key, value in os.environ.items(): 439 | if key.startswith("CUCKOO_"): 440 | env[key] = value 441 | 442 | env["CUCKOO_CWD"] = cwd() 443 | env["CUCKOO_APP"] = os.environ.get("CUCKOO_APP", "") 444 | config = configparser.ConfigParser(env) 445 | 446 | self.env_keys = [] 447 | for key in env.keys(): 448 | self.env_keys.append(key.lower()) 449 | 450 | self.sections = {} 451 | 452 | try: 453 | config.read(cfg or cwd("conf", "%s.conf" % file_name)) 454 | except configparser.ParsingError as e: 455 | raise CuckooConfigurationError( 456 | "There was an error reading in the $CWD/conf/%s.conf " 457 | "configuration file. Most likely there are leading " 458 | "whitespaces in front of one of the key=value lines defined. " 459 | "More information from the original exception: %s" % (file_name, e) 460 | ) 461 | 462 | if file_name not in self.configuration and not loose: 463 | log.error("Unknown config file %s.conf", file_name) 464 | return 465 | 466 | for section in config.sections(): 467 | types = self.get_section_types(file_name, section, strict, loose) 468 | if types is None: 469 | continue 470 | 471 | self.sections[section] = {} 472 | setattr(self, section, self.sections[section]) 473 | 474 | try: 475 | items = config.items(section) 476 | except configparser.InterpolationMissingOptionError as e: 477 | log.error("Missing environment variable(s): %s", e) 478 | raise CuckooConfigurationError("Missing environment variable: %s" % e) 479 | except ValueError as e: 480 | if e.message == "incomplete format key": 481 | raise CuckooConfigurationError( 482 | "One of the fields that you've filled out in " 483 | "$CWD/conf/%s contains the sequence '%(' which is " 484 | "interpreted as environment variable sequence, e.g., " 485 | "'%(PGPASSWORD)s' would locate a PostgreSQL " 486 | "password. Please update the field to correctly " 487 | "state the environment variable or change it in a " 488 | "way that '%(' is no longer in the variable." 489 | ) 490 | raise 491 | 492 | for name, raw_value in items: 493 | if name in self.env_keys: 494 | continue 495 | 496 | if "\n" in raw_value: 497 | wrong_key = "???" 498 | try: 499 | wrong_key = raw_value.split("\n", 1)[1].split()[0] 500 | except: 501 | pass 502 | 503 | raise CuckooConfigurationError( 504 | "There was an error reading in the $CWD/conf/%s.conf " 505 | "configuration file. Namely, there are one or more " 506 | "leading whitespaces before the definition of the " 507 | "'%s' key/value pair in the '%s' section. Please " 508 | "remove those leading whitespaces as Python's default " 509 | "configuration parser is unable to handle those " 510 | "properly." % (file_name, wrong_key, section) 511 | ) 512 | 513 | if not raw and name in types: 514 | # TODO Is this the area where we should be checking the 515 | # configuration values? 516 | # if not types[name].check(raw_value): 517 | # print file_name, section, name, raw_value 518 | # raise 519 | 520 | value = types[name].parse(raw_value) 521 | else: 522 | if not loose: 523 | log.error( 524 | "Type of config parameter %s:%s:%s not found! " 525 | "This may indicate that you've incorrectly filled " 526 | "out the Cuckoo configuration, please double " 527 | "check it.", 528 | file_name, 529 | section, 530 | name, 531 | ) 532 | value = raw_value 533 | 534 | self.sections[section][name] = value 535 | 536 | def get(self, section): 537 | """Get option. 538 | @param section: section to fetch. 539 | @raise CuckooConfigurationError: if section not found. 540 | @return: option value. 541 | """ 542 | if section not in self.sections: 543 | raise CuckooConfigurationError( 544 | "Option %s is not found in configuration" % section 545 | ) 546 | 547 | return self.sections[section] 548 | 549 | @staticmethod 550 | def from_confdir(dirpath, loose=False, sanitize=False): 551 | """Read all the configuration from a configuration directory. If 552 | `sanitize` is set, then black out sensitive fields.""" 553 | ret = {} 554 | for filename in os.listdir(dirpath): 555 | if not filename.endswith(".conf"): 556 | continue 557 | 558 | config_name = filename.rsplit(".", 1)[0] 559 | cfg = Config(config_name, cfg=os.path.join(dirpath, filename), loose=loose) 560 | 561 | ret[config_name] = {} 562 | for section, values in cfg.sections.items(): 563 | ret[config_name][section] = {} 564 | types = cfg.get_section_types(config_name, section, loose=loose) or {} 565 | for key, value in values.items(): 566 | if sanitize and key in types and types[key].sanitize: 567 | value = "*" * 8 568 | 569 | ret[config_name][section][key] = value 570 | return ret 571 | 572 | 573 | def parse_options(options): 574 | """Parse the analysis options field to a dictionary.""" 575 | ret = {} 576 | for field in options.split(","): 577 | if "=" not in field: 578 | continue 579 | 580 | key, value = field.split("=", 1) 581 | ret[key.strip()] = value.strip() 582 | return ret 583 | 584 | 585 | def emit_options(options): 586 | """Emit the analysis options from a dictionary to a string.""" 587 | return ",".join("%s=%s" % (k, v) for k, v in sorted(options.items())) 588 | 589 | 590 | def config(s, cfg=None, strict=False, raw=False, loose=False, check=False): 591 | """Fetch a configuration value, denoted as file:section:key.""" 592 | if s.count(":") != 2: 593 | raise RuntimeError("Invalid configuration entry: %s" % s) 594 | 595 | file_name, section, key = s.split(":") 596 | 597 | if check: 598 | strict = raw = loose = True 599 | 600 | type_ = Config.configuration.get(file_name, {}).get(section, {}).get(key) 601 | if strict and type_ is None: 602 | raise CuckooConfigurationError("No such configuration value exists: %s" % s) 603 | 604 | required = type_ is not None and type_.required 605 | index = file_name, cfg, cwd(), strict, raw, loose 606 | 607 | if index not in _cache: 608 | _cache[index] = Config(file_name, cfg=cfg, strict=strict, raw=raw, loose=loose) 609 | 610 | config = _cache[index] 611 | 612 | if strict and required and section not in config.sections: 613 | raise CuckooConfigurationError( 614 | "Configuration value %s not present! This may indicate that " 615 | "you've incorrectly filled out the Cuckoo configuration, " 616 | "please double check it." % s 617 | ) 618 | 619 | section = config.sections.get(section, {}) 620 | if strict and required and key not in section: 621 | raise CuckooConfigurationError( 622 | "Configuration value %s not present! This may indicate that " 623 | "you've incorrectly filled out the Cuckoo configuration, " 624 | "please double check it." % s 625 | ) 626 | 627 | value = section.get(key, type_.default if type_ else None) 628 | 629 | if check and not type_.check(value): 630 | raise CuckooConfigurationError( 631 | "The configuration value %r found for %s is invalid. Please " 632 | "update your configuration!" % (value, s) 633 | ) 634 | 635 | return value 636 | 637 | 638 | def get_section_types(file_name, section, strict=False): 639 | if section in Config.configuration.get(file_name, {}): 640 | return Config.configuration[file_name][section] 641 | 642 | if "__star__" not in Config.configuration.get(file_name, {}): 643 | return {} 644 | 645 | if strict: 646 | section_, key = Config.configuration[file_name]["__star__"] 647 | if section not in config("%s:%s:%s" % (file_name, section_, key)): 648 | return {} 649 | 650 | if "*" in Config.configuration.get(file_name, {}): 651 | section_types = Config.configuration[file_name]["*"] 652 | # If multiple default values have been provided, pick one. 653 | if isinstance(section_types, (tuple, list)): 654 | section_types = section_types[0] 655 | return section_types 656 | return {} 657 | 658 | 659 | def config2(file_name, section): 660 | keys = get_section_types(file_name, section, strict=True) 661 | if not keys: 662 | raise CuckooConfigurationError( 663 | "No such configuration section exists: %s:%s" % (file_name, section) 664 | ) 665 | 666 | ret = {} 667 | for key in keys: 668 | if key == "__star__" or key == "*": 669 | continue 670 | ret[key] = config("%s:%s:%s" % (file_name, section, key)) 671 | return ret 672 | 673 | 674 | def cast(s, value): 675 | """Cast a configuration value as per its type.""" 676 | if s.count(":") != 2: 677 | raise RuntimeError("Invalid configuration entry: %s" % s) 678 | 679 | file_name, section, key = s.split(":") 680 | type_ = get_section_types(file_name, section).get(key) 681 | if type_ is None: 682 | raise CuckooConfigurationError("No such configuration value exists: %s" % s) 683 | 684 | return type_.parse(value) 685 | 686 | 687 | def read_kv_conf(filepath): 688 | """Read a flat Cuckoo key/value configuration file.""" 689 | ret = {} 690 | for line in open(filepath, "rb"): 691 | line = line.strip() 692 | if not line or line.startswith("#"): 693 | continue 694 | 695 | if "=" not in line: 696 | raise CuckooConfigurationError( 697 | "Invalid flat configuration line: %s (missing '=' character)" % line 698 | ) 699 | 700 | key, raw_value = line.split("=", 1) 701 | key, raw_value = key.replace(".", ":").strip(), raw_value.strip() 702 | try: 703 | value = cast(key, raw_value) 704 | except (CuckooConfigurationError, RuntimeError) as e: 705 | raise CuckooConfigurationError( 706 | "Invalid flat configuration line: %s (error %s)" % (line, e) 707 | ) 708 | 709 | if raw_value and value is None: 710 | raise CuckooConfigurationError( 711 | "Invalid flat configuration entry: %s is None" % key 712 | ) 713 | 714 | a, b, c = key.split(":") 715 | ret[a] = ret.get(a, {}) 716 | ret[a][b] = ret[a].get(b, {}) 717 | ret[a][b][c] = value 718 | return ret 719 | -------------------------------------------------------------------------------- /lib/core/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # ================================================== 4 | # @Time : 2019-05-27 23:51 5 | # @Author : ryuchen 6 | # @Site : 7 | # @File : __init__.py.py 8 | # @Desc : 9 | # ================================================== -------------------------------------------------------------------------------- /lib/core/backend/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # ================================================== 4 | # @Time : 2019-05-30 17:10 5 | # @Author : ryuchen 6 | # @File : __init__.py.py 7 | # @Desc : 8 | # ================================================== -------------------------------------------------------------------------------- /lib/core/manager/GuestManager.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # ================================================== 4 | # @Time : 2019-05-30 17:12 5 | # @Author : ryuchen 6 | # @File : GuestManager.py 7 | # @Desc : We abandon the old agent, only adapt for new guest agent 8 | # ================================================== 9 | import io 10 | import os 11 | import json 12 | import time 13 | import socket 14 | import zipfile 15 | import logging 16 | import requests 17 | import datetime 18 | 19 | from lib.common import config, parse_options 20 | from lib.common import ( 21 | CuckooGuestError, CuckooGuestCriticalTimeout 22 | ) 23 | from lib.core import Database 24 | from misc import cwd 25 | 26 | log = logging.getLogger(__name__) 27 | db = Database() 28 | 29 | 30 | def analyzer_zipfile(platform, monitor): 31 | """Creates the Zip file that is sent to the Guest.""" 32 | t = time.time() 33 | 34 | zip_data = io.BytesIO() 35 | zip_file = zipfile.ZipFile(zip_data, "w", zipfile.ZIP_STORED) 36 | 37 | # Select the proper analyzer's folder according to the operating 38 | # system associated with the current machine. 39 | root = cwd("analyzer", platform) 40 | root_len = len(os.path.abspath(root)) 41 | 42 | if not os.path.exists(root): 43 | log.error("No valid analyzer found at path: %s", root) 44 | raise CuckooGuestError( 45 | "No valid analyzer found for %s platform!" % platform 46 | ) 47 | 48 | # Walk through everything inside the analyzer's folder and write 49 | # them to the zip archive. 50 | for root, dirs, files in os.walk(root): 51 | archive_root = os.path.abspath(root)[root_len:] 52 | for name in files: 53 | path = os.path.join(root, name) 54 | archive_name = os.path.join(archive_root, name) 55 | zip_file.write(path, archive_name) 56 | 57 | # Include the chosen monitoring component and any additional files. 58 | if platform == "windows": 59 | dirpath = cwd("monitor", monitor) 60 | 61 | # Generally speaking we should no longer be getting symbolic links for 62 | # "latest" anymore, so in the case of a file; follow it. 63 | if os.path.isfile(dirpath): 64 | monitor = os.path.basename(open(dirpath, "rb").read().strip()) 65 | dirpath = cwd("monitor", monitor) 66 | 67 | for name in os.listdir(dirpath): 68 | zip_file.write( 69 | os.path.join(dirpath, name), os.path.join("bin", name) 70 | ) 71 | 72 | # Dump compiled "dumpmem" Yara rules for zer0m0n usage. 73 | zip_file.write(cwd("stuff", "dumpmem.yarac"), "bin/rules.yarac") 74 | 75 | zip_file.close() 76 | data = zip_data.getvalue() 77 | 78 | if time.time() - t > 10: 79 | log.warning( 80 | "It took more than 10 seconds to build the Analyzer Zip for the " 81 | "Guest. This might be a serious performance penalty. Is your " 82 | "analyzer/windows/ directory bloated with unnecessary files?" 83 | ) 84 | 85 | return data 86 | 87 | 88 | class GuestManager(object): 89 | """This class represents the new Guest Manager. It operates on the new 90 | Cuckoo Agent which features a more abstract but more feature-rich API.""" 91 | 92 | def __init__(self, machine, task_id, analysis_manager): 93 | self.machine = machine 94 | self.task_id = task_id 95 | self.analysis_manager = analysis_manager 96 | self.timeout = None 97 | 98 | # We maintain the path of the Cuckoo Analyzer on the host. 99 | self.analyzer_path = None 100 | self.environ = {} 101 | 102 | self.options = {} 103 | 104 | @property 105 | def aux(self): 106 | return self.analysis_manager.aux 107 | 108 | def get(self, method, **kwargs): 109 | """Simple wrapper around requests.get().""" 110 | do_raise = kwargs.pop("do_raise", True) 111 | url = "http://%s:%s%s" % (self.machine.addr, self.machine.port, method) 112 | session = requests.Session() 113 | session.trust_env = False 114 | session.proxies = None 115 | 116 | try: 117 | r = session.get(url, **kwargs) 118 | except requests.ConnectionError: 119 | raise CuckooGuestError( 120 | "Cuckoo Agent failed without error status, please try " 121 | "upgrading to the latest version of agent.py (>= 0.8) and " 122 | "notify us if the issue persists." 123 | ) 124 | 125 | do_raise and r.raise_for_status() 126 | return r 127 | 128 | def post(self, method, *args, **kwargs): 129 | """Simple wrapper around requests.post().""" 130 | url = "http://%s:%s%s" % (self.machine.addr, self.machine.port, method) 131 | session = requests.Session() 132 | session.trust_env = False 133 | session.proxies = None 134 | 135 | try: 136 | r = session.post(url, *args, **kwargs) 137 | except requests.ConnectionError: 138 | raise CuckooGuestError( 139 | "Cuckoo Agent failed without error status, please try " 140 | "upgrading to the latest version of agent.py (>= 0.8) and " 141 | "notify us if the issue persists." 142 | ) 143 | 144 | r.raise_for_status() 145 | return r 146 | 147 | def wait_available(self): 148 | """Wait until the Virtual Machine is available for usage.""" 149 | end = time.time() + self.timeout 150 | 151 | while db.guest_get_status(self.task_id) == "starting": 152 | try: 153 | socket.create_connection((self.machine.addr, self.machine.port), 1).close() 154 | break 155 | except socket.timeout: 156 | log.debug("%s: not ready yet", self.machine.label) 157 | except socket.error: 158 | log.debug("%s: not ready yet", self.machine.label) 159 | time.sleep(1) 160 | 161 | if time.time() > end: 162 | raise CuckooGuestCriticalTimeout( 163 | "Machine %s: the guest initialization hit the critical " 164 | "timeout, analysis aborted." % self.machine.label 165 | ) 166 | 167 | def query_environ(self): 168 | """Query the environment of the Agent in the Virtual Machine.""" 169 | self.environ = self.get("/environ").json()["environ"] 170 | 171 | def determine_analyzer_path(self): 172 | """Determine the path of the analyzer. Basically creating a temporary 173 | directory in the systemdrive, i.e., C:\\.""" 174 | systemdrive = self.determine_system_drive() 175 | 176 | options = parse_options(self.options["options"]) 177 | if options.get("analpath"): 178 | dirpath = systemdrive + options["analpath"] 179 | r = self.post("/mkdir", data={"dirpath": dirpath}) 180 | self.analyzer_path = dirpath 181 | else: 182 | r = self.post("/mkdtemp", data={"dirpath": systemdrive}) 183 | self.analyzer_path = r.json()["dirpath"] 184 | 185 | def determine_system_drive(self): 186 | if self.machine.platform == "windows": 187 | return "%s/" % self.environ["SYSTEMDRIVE"] 188 | return "/" 189 | 190 | def determine_temp_path(self): 191 | if self.machine.platform == "windows": 192 | return self.environ["TEMP"] 193 | return "/tmp" 194 | 195 | def upload_analyzer(self, monitor): 196 | """Upload the analyzer to the Virtual Machine.""" 197 | zip_data = analyzer_zipfile(self.machine.platform, monitor) 198 | 199 | log.debug( 200 | "Uploading analyzer to guest (id=%s, ip=%s, monitor=%s, size=%d)", 201 | self.machine.label, self.machine.addr, monitor, len(zip_data) 202 | ) 203 | 204 | self.determine_analyzer_path() 205 | data = { 206 | "dirpath": self.analyzer_path, 207 | } 208 | self.post("/extract", files={"zipfile": zip_data}, data=data) 209 | 210 | def add_config(self, options): 211 | """Upload the analysis.conf for this task to the Virtual Machine.""" 212 | config = ["[analysis]", ] 213 | for key, value in options.items(): 214 | # Encode datetime objects the way xmlrpc encodes them. 215 | if isinstance(value, datetime.datetime): 216 | config.append("%s = %s" % (key, value.strftime("%Y%m%dT%H:%M:%S"))) 217 | else: 218 | config.append("%s = %s" % (key, value)) 219 | 220 | data = { 221 | "filepath": os.path.join(self.analyzer_path, "analysis.conf"), 222 | } 223 | self.post("/store", files={"file": "\n".join(config)}, data=data) 224 | 225 | def start_analysis(self, options, monitor): 226 | """Start the analysis by uploading all required files. 227 | @param options: the task options 228 | @param monitor: identifier of the monitor to be used. 229 | """ 230 | log.info("Starting analysis on guest (id=%s, ip=%s)", self.machine.label, self.machine.addr) 231 | 232 | self.options = options 233 | self.timeout = options["timeout"] + config("cuckoo:timeouts:critical") 234 | 235 | # Wait for the agent to come alive. 236 | self.wait_available() 237 | 238 | # Could be beautified a bit, but basically we have to perform the 239 | # same check here as we did in wait_available(). 240 | if db.guest_get_status(self.task_id) != "starting": 241 | return 242 | 243 | # Check whether this is the new Agent or the old one (by looking at 244 | # the status code of the index page). 245 | r = self.get("/", do_raise=False) 246 | 247 | if r.status_code != 200: 248 | log.critical( 249 | "While trying to determine the Agent version that your VM is " 250 | "running we retrieved an unexpected HTTP status code: %s. If " 251 | "this is a false positive, please report this issue to the " 252 | "Cuckoo Developers. HTTP response headers: %s", 253 | r.status_code, json.dumps(dict(r.headers)), 254 | ) 255 | db.guest_set_status(self.task_id, "failed") 256 | return 257 | 258 | try: 259 | status = r.json() 260 | version = status.get("version") 261 | features = status.get("features", []) 262 | except: 263 | log.critical( 264 | "We were unable to detect either the Old or New Agent in the " 265 | "Guest VM, are you sure you have set it up correctly? Please " 266 | "go through the documentation once more and otherwise inform " 267 | "the Cuckoo Developers of your issue." 268 | ) 269 | db.guest_set_status(self.task_id, "failed") 270 | return 271 | 272 | log.info("Guest is running Cuckoo Agent %s (id=%s, ip=%s)", version, self.machine.label, self.machine.addr) 273 | 274 | # Pin the Agent to our IP address so that it is not accessible by 275 | # other Virtual Machines etc. 276 | if "pinning" in features: 277 | self.get("/pinning") 278 | 279 | # Obtain the environment variables. 280 | self.query_environ() 281 | 282 | # Upload the analyzer. 283 | self.upload_analyzer(monitor) 284 | 285 | # Pass along the analysis.conf file. 286 | self.add_config(options) 287 | 288 | # Allow Auxiliary modules to prepare the Guest. 289 | self.aux.callback("prepare_guest") 290 | 291 | # If the target is a file, upload it to the guest. 292 | if options["category"] == "file" or options["category"] == "archive": 293 | data = { 294 | "filepath": os.path.join(self.determine_temp_path(), options["file_name"]), 295 | } 296 | files = { 297 | "file": ("sample.bin", open(options["target"], "rb")), 298 | } 299 | self.post("/store", files=files, data=data) 300 | 301 | if "execpy" in features: 302 | data = { 303 | "filepath": "%s/analyzer.py" % self.analyzer_path, 304 | "async": "yes", 305 | "cwd": self.analyzer_path, 306 | } 307 | self.post("/execpy", data=data) 308 | else: 309 | # Execute the analyzer that we just uploaded. 310 | data = { 311 | "command": "C:\\Python27\\pythonw.exe %s\\analyzer.py" % self.analyzer_path, 312 | "async": "yes", 313 | "cwd": self.analyzer_path, 314 | } 315 | self.post("/execute", data=data) 316 | 317 | def wait_for_completion(self): 318 | end = time.time() + self.timeout 319 | 320 | while db.guest_get_status(self.task_id) == "running": 321 | log.debug("%s: analysis still processing", self.machine.label) 322 | 323 | time.sleep(1) 324 | 325 | # If the analysis hits the critical timeout, just return straight 326 | # away and try to recover the analysis results from the guest. 327 | if time.time() > end: 328 | log.info("%s: end of analysis reached!", self.machine.label) 329 | return 330 | 331 | try: 332 | status = self.get("/status", timeout=5).json() 333 | except Exception as e: 334 | log.info("Virtual Machine /status failed (%r)", e) 335 | # this might fail due to timeouts or just temporary network issues 336 | # thus we don't want to abort the analysis just yet and wait for things to 337 | # recover 338 | continue 339 | 340 | if status["status"] == "complete": 341 | log.info("%s: analysis completed successfully", self.machine.label) 342 | return 343 | elif status["status"] == "exception": 344 | log.warning("%s: analysis caught an exception\n%s", self.machine.label, status["description"]) 345 | return 346 | -------------------------------------------------------------------------------- /lib/core/manager/ResultManager.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # ================================================== 4 | # @Time : 2019-06-20 10:14 5 | # @Author : ryuchen 6 | # @File : ResultManager.py 7 | # @Desc : 8 | # ================================================== 9 | 10 | import os 11 | import json 12 | import errno 13 | import socket 14 | import logging 15 | import threading 16 | import gevent.pool 17 | import gevent.server 18 | import gevent.socket 19 | 20 | from lib.common.config import config 21 | from cuckoo.common.abstracts import ProtocolHandler 22 | from lib.exceptions.critical import CuckooCriticalError 23 | from lib.exceptions.operation import CuckooOperationalError 24 | from cuckoo.common.files import open_exclusive 25 | from lib.utils.utils import Singleton 26 | from cuckoo.core.log import task_log_start, task_log_stop 27 | from cuckoo.misc import cwd 28 | 29 | log = logging.getLogger(__name__) 30 | 31 | # Maximum line length to read for netlog messages, to avoid memory exhaustion 32 | MAX_NETLOG_LINE = 4 * 1024 33 | 34 | # Maximum number of bytes to buffer for a single connection 35 | BUFSIZE = 16 * 1024 36 | 37 | # Directories in which analysis-related files will be stored; also acts as 38 | # whitelist 39 | RESULT_UPLOADABLE = ("files", "shots", "buffer", "extracted", "memory") 40 | RESULT_DIRECTORIES = RESULT_UPLOADABLE + ("reports", "logs") 41 | 42 | # Prevent malicious clients from using potentially dangerous filenames 43 | # E.g. C API confusion by using null, or using the colon on NTFS (Alternate 44 | # Data Streams); XXX: just replace illegal chars? 45 | BANNED_PATH_CHARS = b"\x00:" 46 | 47 | 48 | def netlog_sanitize_fname(path): 49 | """Validate agent-provided path for result files""" 50 | path = path.replace("\\", "/") 51 | dir_part, name = os.path.split(path) 52 | if dir_part not in RESULT_UPLOADABLE: 53 | raise CuckooOperationalError("Netlog client requested banned path: %r" % path) 54 | if any(c in BANNED_PATH_CHARS for c in name): 55 | for c in BANNED_PATH_CHARS: 56 | path = path.replace(c, "X") 57 | 58 | return path 59 | 60 | 61 | class HandlerContext(object): 62 | """Holds context for protocol handlers. 63 | 64 | Can safely be cancelled from another thread, though in practice this will 65 | not occur often -- usually the connection between VM and the ResultServer 66 | will be reset during shutdown.""" 67 | 68 | def __init__(self, task_id, storagepath, sock): 69 | self.task_id = task_id 70 | self.command = None 71 | 72 | # The path where artifacts will be stored 73 | self.storagepath = storagepath 74 | self.sock = sock 75 | self.buf = "" 76 | 77 | def __repr__(self): 78 | return "" % self.command 79 | 80 | def cancel(self): 81 | """Cancel this context; gevent might complain about this with an 82 | exception later on.""" 83 | try: 84 | self.sock.shutdown(socket.SHUT_RD) 85 | except socket.error: 86 | pass 87 | 88 | def read(self): 89 | try: 90 | return self.sock.recv(16384) 91 | except socket.error as e: 92 | if e.errno == errno.EBADF: 93 | return "" 94 | 95 | if e.errno != errno.ECONNRESET: 96 | raise 97 | log.debug("Task #%s had connection reset for %r", self.task_id, self) 98 | return "" 99 | 100 | def drain_buffer(self): 101 | """Drain buffer and end buffering""" 102 | buf, self.buf = self.buf, None 103 | return buf 104 | 105 | def read_newline(self): 106 | """Read until the next newline character, but never more than 107 | `MAX_NETLOG_LINE`.""" 108 | while True: 109 | pos = self.buf.find("\n") 110 | if pos < 0: 111 | if len(self.buf) >= MAX_NETLOG_LINE: 112 | raise CuckooOperationalError("Received overly long line") 113 | buf = self.read() 114 | if buf == "": 115 | raise EOFError 116 | self.buf += buf 117 | continue 118 | line, self.buf = self.buf[:pos], self.buf[pos + 1 :] 119 | return line 120 | 121 | def copy_to_fd(self, fd, max_size=None): 122 | if max_size: 123 | fd = WriteLimiter(fd, max_size) 124 | fd.write(self.drain_buffer()) 125 | while True: 126 | buf = self.read() 127 | if buf == "": 128 | break 129 | fd.write(buf) 130 | fd.flush() 131 | 132 | 133 | class WriteLimiter(object): 134 | def __init__(self, fd, remain): 135 | self.fd = fd 136 | self.remain = remain 137 | self.warned = False 138 | 139 | def write(self, buf): 140 | size = len(buf) 141 | write = min(size, self.remain) 142 | if write: 143 | self.fd.write(buf[:write]) 144 | self.remain -= write 145 | if size and size != write: 146 | if not self.warned: 147 | log.warning( 148 | "Uploaded file length larger than upload_max_size, " 149 | "stopping upload." 150 | ) 151 | self.fd.write("... (truncated)") 152 | self.warned = True 153 | 154 | def flush(self): 155 | self.fd.flush() 156 | 157 | 158 | class FileUpload(ProtocolHandler): 159 | def init(self): 160 | self.upload_max_size = config("cuckoo:resultserver:upload_max_size") 161 | self.storagepath = self.handler.storagepath 162 | self.fd = None 163 | self.filelog = os.path.join(self.handler.storagepath, "files.json") 164 | 165 | def handle(self): 166 | # Read until newline for file path, e.g., 167 | # shots/0001.jpg or files/9498687557/libcurl-4.dll.bin 168 | self.handler.sock.settimeout(30) 169 | dump_path = netlog_sanitize_fname(self.handler.read_newline()) 170 | 171 | if self.version and self.version >= 2: 172 | # NB: filepath is only used as metadata 173 | filepath = self.handler.read_newline() 174 | pids = map(int, self.handler.read_newline().split()) 175 | else: 176 | filepath, pids = None, [] 177 | 178 | log.debug("Task #%s: File upload for %r", self.task_id, dump_path) 179 | file_path = os.path.join(self.storagepath, dump_path.decode("utf-8")) 180 | 181 | try: 182 | self.fd = open_exclusive(file_path) 183 | except OSError as e: 184 | if e.errno == errno.EEXIST: 185 | raise CuckooOperationalError( 186 | "Analyzer for task #%s tried to " 187 | "overwrite an existing file" % self.task_id 188 | ) 189 | raise 190 | 191 | # Append-writes are atomic 192 | with open(self.filelog, "a+b") as f: 193 | print( 194 | json.dumps({"path": dump_path, "filepath": filepath, "pids": pids}), 195 | file=f, 196 | ) 197 | 198 | self.handler.sock.settimeout(None) 199 | try: 200 | return self.handler.copy_to_fd(self.fd, self.upload_max_size) 201 | finally: 202 | log.debug("Task #%s uploaded file length: %s", self.task_id, self.fd.tell()) 203 | 204 | 205 | class LogHandler(ProtocolHandler): 206 | """The live analysis log. Can only be opened once in a single session.""" 207 | 208 | def init(self): 209 | self.logpath = os.path.join(self.handler.storagepath, "analysis.log") 210 | try: 211 | self.fd = open_exclusive(self.logpath, bufsize=1) 212 | except OSError: 213 | log.error( 214 | "Task #%s: attempted to reopen live log analysis.log.", self.task_id 215 | ) 216 | return 217 | 218 | log.debug("Task #%s: live log analysis.log initialized.", self.task_id) 219 | 220 | def handle(self): 221 | if self.fd: 222 | return self.handler.copy_to_fd(self.fd) 223 | 224 | 225 | class BsonStore(ProtocolHandler): 226 | def init(self): 227 | # We cheat a little bit through the "version" variable, but that's 228 | # acceptable and backwards compatible (for now). Backwards compatible 229 | # in the sense that newer Cuckoo Monitor binaries work with older 230 | # versions of Cuckoo, the other way around doesn't apply here. 231 | if self.version is None: 232 | log.warning( 233 | "Agent is sending BSON files without PID parameter, " 234 | "you should probably update it" 235 | ) 236 | self.fd = None 237 | return 238 | 239 | self.fd = open( 240 | os.path.join(self.handler.storagepath, "logs", "%d.bson" % self.version), 241 | "wb", 242 | ) 243 | 244 | def handle(self): 245 | """Read a BSON stream, attempting at least basic validation, and 246 | log failures.""" 247 | log.debug("Task #%s is sending a BSON stream", self.task_id) 248 | if self.fd: 249 | return self.handler.copy_to_fd(self.fd) 250 | 251 | 252 | class GeventResultServerWorker(gevent.server.StreamServer): 253 | """The new ResultServer, providing a huge performance boost as well as 254 | implementing a new dropped file storage format avoiding small fd limits. 255 | 256 | The old ResultServer would start a new thread per socket, greatly impacting 257 | the overall performance of Cuckoo Sandbox. The new ResultServer uses 258 | so-called Greenlets, low overhead green-threads by Gevent, imposing much 259 | less kernel overhead. 260 | 261 | Furthermore, instead of writing each dropped file to its own location (in 262 | $CWD/storage/analyses//files/_filename.ext) it's 263 | capable of storing all dropped files in a streamable container format. This 264 | is one of various steps to start being able to use less fd's in Cuckoo. 265 | """ 266 | 267 | commands = {"BSON": BsonStore, "FILE": FileUpload, "LOG": LogHandler} 268 | task_mgmt_lock = threading.Lock() 269 | 270 | def __init__(self, *args, **kwargs): 271 | super(GeventResultServerWorker, self).__init__(*args, **kwargs) 272 | 273 | # Store IP address to task_id mapping 274 | self.tasks = {} 275 | 276 | # Store running handlers for task_id 277 | self.handlers = {} 278 | 279 | def do_run(self): 280 | self.serve_forever() 281 | 282 | def add_task(self, task_id, ipaddr): 283 | with self.task_mgmt_lock: 284 | self.tasks[ipaddr] = task_id 285 | log.debug("Now tracking machine %s for task #%s", ipaddr, task_id) 286 | 287 | def del_task(self, task_id, ipaddr): 288 | """Delete ResultServer state and abort pending RequestHandlers. Since 289 | we're about to shutdown the VM, any remaining open connections can 290 | be considered a bug from the VM side, since all connections should 291 | have been closed after the analyzer signalled completion.""" 292 | with self.task_mgmt_lock: 293 | if self.tasks.pop(ipaddr, None) is None: 294 | log.warning( 295 | "ResultServer did not have a task with ID %s and IP %s", 296 | task_id, 297 | ipaddr, 298 | ) 299 | else: 300 | log.debug("Stopped tracking machine %s for task #%s", ipaddr, task_id) 301 | ctxs = self.handlers.pop(task_id, set()) 302 | for ctx in ctxs: 303 | log.debug("Cancel %s for task %r", ctx, task_id) 304 | ctx.cancel() 305 | 306 | def handle(self, sock, addr): 307 | """Handle the incoming connection. 308 | Gevent will close the socket when the function returns.""" 309 | ipaddr = addr[0] 310 | 311 | with self.task_mgmt_lock: 312 | task_id = self.tasks.get(ipaddr) 313 | if not task_id: 314 | log.warning("ResultServer did not have a task for IP %s", ipaddr) 315 | return 316 | 317 | storagepath = cwd(analysis=task_id) 318 | ctx = HandlerContext(task_id, storagepath, sock) 319 | task_log_start(task_id) 320 | try: 321 | try: 322 | protocol = self.negotiate_protocol(task_id, ctx) 323 | except EOFError: 324 | return 325 | 326 | # Registering the context allows us to abort the handler by 327 | # shutting down its socket when the task is deleted; this should 328 | # prevent lingering sockets 329 | with self.task_mgmt_lock: 330 | # NOTE: the task may have been cancelled during the negotation 331 | # protocol and a different task for that IP address may have 332 | # been registered 333 | if self.tasks.get(ipaddr) != task_id: 334 | log.warning( 335 | "Task #%s for IP %s was cancelled during " "negotiation", 336 | task_id, 337 | ipaddr, 338 | ) 339 | return 340 | s = self.handlers.setdefault(task_id, set()) 341 | s.add(ctx) 342 | 343 | try: 344 | with protocol: 345 | protocol.handle() 346 | except CuckooOperationalError as e: 347 | log.error(e) 348 | finally: 349 | with self.task_mgmt_lock: 350 | s.discard(ctx) 351 | ctx.cancel() 352 | if ctx.buf: 353 | # This is usually not a good sign 354 | log.warning( 355 | "Task #%s with protocol %s has unprocessed " 356 | "data before getting disconnected", 357 | task_id, 358 | protocol, 359 | ) 360 | 361 | finally: 362 | task_log_stop(task_id) 363 | 364 | def negotiate_protocol(self, task_id, ctx): 365 | header = ctx.read_newline() 366 | if " " in header: 367 | command, version = header.split() 368 | version = int(version) 369 | else: 370 | command, version = header, None 371 | klass = self.commands.get(command) 372 | if not klass: 373 | log.warning( 374 | "Task #%s: unknown netlog protocol requested (%r), " 375 | "terminating connection.", 376 | task_id, 377 | command, 378 | ) 379 | return 380 | ctx.command = command 381 | return klass(task_id, ctx, version) 382 | 383 | 384 | class ResultServer(object): 385 | """Manager for the ResultServer worker and task state.""" 386 | 387 | __metaclass__ = Singleton 388 | 389 | def __init__(self): 390 | ip = config("cuckoo:resultserver:ip") 391 | port = config("cuckoo:resultserver:port") 392 | pool_size = config("cuckoo:resultserver:pool_size") 393 | 394 | sock = gevent.socket.socket(socket.AF_INET, socket.SOCK_STREAM) 395 | sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 396 | 397 | try: 398 | sock.bind((ip, port)) 399 | except (OSError, socket.error) as e: 400 | if e.errno == errno.EADDRINUSE: 401 | raise CuckooCriticalError( 402 | "Cannot bind ResultServer on port %d " 403 | "because it was in use, bailing." % port 404 | ) 405 | elif e.errno == errno.EADDRNOTAVAIL: 406 | raise CuckooCriticalError( 407 | "Unable to bind ResultServer on %s:%s %s. This " 408 | "usually happens when you start Cuckoo without " 409 | "bringing up the virtual interface associated with " 410 | "the ResultServer IP address. Please refer to " 411 | "https://cuckoo.sh/docs/faq/#troubles-problem " 412 | "for more information." % (ip, port, e) 413 | ) 414 | else: 415 | raise CuckooCriticalError( 416 | "Unable to bind ResultServer on %s:%s: %s" % (ip, port, e) 417 | ) 418 | 419 | # We allow user to specify port 0 to get a random port, report it back 420 | # here 421 | _, self.port = sock.getsockname() 422 | sock.listen(128) 423 | 424 | self.thread = threading.Thread( 425 | target=self.create_server, args=(sock, pool_size) 426 | ) 427 | self.thread.daemon = True 428 | self.thread.start() 429 | 430 | def add_task(self, task, machine): 431 | """Register a task/machine with the ResultServer.""" 432 | self.instance.add_task(task.id, machine.ip) 433 | 434 | def del_task(self, task, machine): 435 | """Delete running task and cancel existing handlers.""" 436 | self.instance.del_task(task.id, machine.ip) 437 | 438 | def create_server(self, sock, pool_size): 439 | if pool_size: 440 | pool = gevent.pool.Pool(pool_size) 441 | else: 442 | pool = "default" 443 | self.instance = GeventResultServerWorker(sock, spawn=pool) 444 | self.instance.do_run() 445 | -------------------------------------------------------------------------------- /lib/core/manager/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # ================================================== 4 | # @Time : 2019-05-30 17:11 5 | # @Author : ryuchen 6 | # @File : __init__.py.py 7 | # @Desc : 8 | # ================================================== -------------------------------------------------------------------------------- /lib/core/service/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # ================================================== 4 | # @Time : 2019-06-20 11:05 5 | # @Author : ryuchen 6 | # @File : __init__.py.py 7 | # @Desc : 8 | # ================================================== -------------------------------------------------------------------------------- /lib/defines/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # ================================================== 4 | # @Time : 2019-06-20 11:06 5 | # @Author : ryuchen 6 | # @File : __init__.py.py 7 | # @Desc : 8 | # ================================================== -------------------------------------------------------------------------------- /lib/defines/context.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # ================================================== 4 | # @Time : 2019-05-30 19:44 5 | # @Author : ryuchen 6 | # @File : constants.py 7 | # @Desc : 8 | # ================================================== 9 | import os 10 | 11 | SANDBOX_GUEST_PORT = 8000 12 | SANDBOX_GUEST_INIT = 0x001 13 | SANDBOX_GUEST_RUNNING = 0x002 14 | SANDBOX_GUEST_COMPLETED = 0x003 15 | SANDBOX_GUEST_FAILED = 0x004 16 | 17 | SANDBOX_CURRENT_DIR = os.path.abspath(os.path.dirname(__file__)) 18 | SANDBOX_CONFIG_DIR = os.path.normpath(os.path.join(SANDBOX_CURRENT_DIR, "..", "..", "etc")) 19 | SANDBOX_DOC_DIR = os.path.normpath(os.path.join(SANDBOX_CURRENT_DIR, "..", "..", "doc")) 20 | 21 | GITHUB_URL = "https://github.com/cuckoosandbox/cuckoo" 22 | ISSUES_PAGE_URL = "https://github.com/cuckoosandbox/cuckoo/issues" 23 | DOCS_URL = "https://cuckoo.sh/docs" 24 | 25 | 26 | def faq(entry): 27 | return "%s/faq/index.html#%s" % (DOCS_URL, entry) 28 | -------------------------------------------------------------------------------- /lib/defines/defines.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # ================================================== 4 | # @Time : 2019-05-31 17:59 5 | # @Author : ryuchen 6 | # @File : defines.py 7 | # @Desc : 8 | # ================================================== 9 | 10 | REG_NONE = 0 11 | REG_SZ = 1 12 | REG_EXPAND_SZ = 2 13 | REG_BINARY = 3 14 | REG_DWORD_LITTLE_ENDIAN = 4 15 | REG_DWORD = 4 16 | REG_DWORD_BIG_ENDIAN = 5 17 | 18 | # Windows specific process rights 19 | # https://msdn.microsoft.com/en-us/library/ms684880(v=vs.85).aspx 20 | WIN_PROCESS_QUERY_INFORMATION = 0x0400 21 | 22 | # Windows specific error codes 23 | # https://msdn.microsoft.com/en-us/library/windows/desktop 24 | # /ms683189(v=vs.85).aspx 25 | WIN_ERR_STILL_ALIVE = 259 26 | -------------------------------------------------------------------------------- /lib/defines/types.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # ======================================================== 4 | # @Author: Ryuchen 5 | # @Time: 2019/06/20-22:45 6 | # @Site: https://ryuchen.github.io 7 | # @Contact: chenhaom1993@hotmail.com 8 | # @Copyright: Copyright (C) 2019-2020 Panda-Sandbox. 9 | # ======================================================== 10 | """ 11 | Basic variables type setting to protect every setting is correct set 12 | """ 13 | 14 | import abc 15 | 16 | from numbers import Number 17 | 18 | from lib.exceptions.critical import PandaStartupError 19 | 20 | 21 | class Type(metaclass=abc.ABCMeta): 22 | """ Base Class for Panda-Sandbox Type Definitions """ 23 | 24 | __doc__ = """ 25 | Must Implement this class for subtype to create new instance. 26 | Initialise its with params: 27 | :param default::默认值 28 | :param allow_empty::可取空 29 | @Must implement with below methods: 30 | :method parse::转化 31 | :method check::校验 32 | """ 33 | 34 | def __init__(self, default=None, allow_empty=False): 35 | self.default = default 36 | self.allow_empty = allow_empty 37 | 38 | self._value = None 39 | 40 | def __set__(self, instance, value): 41 | if self.check(value): 42 | self._value = value 43 | else: 44 | self._value = self.parse(value) 45 | 46 | def __str__(self): 47 | if not self._value: 48 | return str(self.default) 49 | return str(self._value) 50 | 51 | @abc.abstractmethod 52 | def parse(self, value): 53 | """ parse a raw input value to correct type value and locate in the value range """ 54 | 55 | @abc.abstractmethod 56 | def check(self, value): 57 | """ check the type of a raw value whether we need this type in this instance """ 58 | 59 | 60 | class Int(Type): 61 | """ Integer Type Definition class """ 62 | 63 | __doc__ = """ 64 | Initialise its with params below: 65 | :param default::默认值 66 | :param allow_empty::可取空 67 | :param v_range::范围 eg: (1, 10) 68 | """ 69 | 70 | def __init__(self, default, allow_empty, v_range=None): 71 | if v_range: 72 | (self.min_value, self.max_value) = v_range 73 | if self.min_value > self.max_value: 74 | raise PandaStartupError("value range incorrect") 75 | else: 76 | (self.min_value, self.max_value) = (None, None) 77 | super(Int, self).__init__(default, allow_empty) 78 | 79 | def parse(self, value): 80 | def num_range(ctx, val): 81 | _fin = val 82 | if ctx.min_value and val < ctx.min_value: 83 | _fin = ctx.min_value 84 | 85 | if ctx.max_value and ctx.max_value < val: 86 | _fin = ctx.max_value 87 | return _fin 88 | 89 | if isinstance(value, Number): 90 | if isinstance(value, bool): 91 | return 1 if value else 0 92 | 93 | return num_range(self, value) 94 | 95 | if isinstance(value, str) and value.isdigit(): 96 | _value = int(value) 97 | return num_range(self, _value) 98 | else: 99 | return self.default 100 | 101 | def check(self, value): 102 | if isinstance(value, Number): 103 | if isinstance(value, bool): 104 | return False 105 | 106 | if self.min_value and value < self.min_value: 107 | return False 108 | 109 | if self.max_value and self.max_value < value: 110 | return False 111 | else: 112 | return False 113 | 114 | return True 115 | 116 | 117 | class String(Type): 118 | """ String Type Definition class """ 119 | 120 | __doc__ = """ 121 | Initialise its with params below: 122 | :param default::默认值 123 | :param allow_empty::可取空 124 | """ 125 | 126 | def parse(self, value): 127 | return str(value).strip() if value else self.default 128 | 129 | def check(self, value): 130 | return isinstance(value, str) 131 | 132 | 133 | class Boolean(Type): 134 | """ Boolean Type Definition class """ 135 | 136 | __doc__ = """ 137 | Initialise its with params below: 138 | :param default::默认值 139 | :param allow_empty::可取空 140 | """ 141 | 142 | def parse(self, value): 143 | if value in ("true", "True", "yes", "1", "on", "open"): 144 | return True 145 | if value in ("false", "False", "no", "0", "off", "close"): 146 | return False 147 | return self.default 148 | 149 | def check(self, value): 150 | return isinstance(value, bool) 151 | -------------------------------------------------------------------------------- /lib/exceptions/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # ================================================== 4 | # @Time : 2019-06-20 11:06 5 | # @Author : ryuchen 6 | # @File : __init__.py.py 7 | # @Desc : 8 | # ================================================== -------------------------------------------------------------------------------- /lib/exceptions/critical.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # ================================================== 4 | # @Time : 2019-06-20 22:59 5 | # @Author : ryuchen 6 | # @File : critical.py 7 | # @Desc : 8 | # ================================================== 9 | 10 | 11 | class PandaCriticalError(Exception): 12 | """Panda struggle in a critical error.""" 13 | 14 | 15 | class PandaStartupError(PandaCriticalError): 16 | """Error starting up Panda.""" 17 | 18 | 19 | class PandaDatabaseError(PandaCriticalError): 20 | """Panda database error.""" 21 | 22 | 23 | class PandaDependencyError(PandaCriticalError): 24 | """Missing dependency error.""" 25 | 26 | 27 | class PandaConfigurationError(PandaCriticalError): 28 | """Invalid configuration error.""" 29 | -------------------------------------------------------------------------------- /lib/exceptions/operation.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # ================================================== 4 | # @Time : 2019-06-20 22:59 5 | # @Author : ryuchen 6 | # @File : operation.py 7 | # @Desc : 8 | # ================================================== 9 | 10 | 11 | class PandaOperationalError(Exception): 12 | """Panda operation error.""" 13 | 14 | 15 | class PandaMachineError(PandaOperationalError): 16 | """Error managing analysis machine.""" 17 | 18 | 19 | class PandaMissingMachineError(PandaMachineError): 20 | """No such machine exists.""" 21 | 22 | 23 | class PandaMachineSnapshotError(PandaMachineError): 24 | """Error restoring snapshot from machine.""" 25 | 26 | 27 | class PandaAnalysisError(PandaOperationalError): 28 | """Error during analysis.""" 29 | 30 | 31 | class PandaProcessingError(PandaOperationalError): 32 | """Error in processor module.""" 33 | 34 | 35 | class PandaReportError(PandaOperationalError): 36 | """Error in reporting module.""" 37 | 38 | 39 | class PandaGuestError(PandaOperationalError): 40 | """Panda guest agent error.""" 41 | 42 | 43 | class PandaGuestCriticalTimeout(PandaGuestError): 44 | """The Host was unable to connect to the Guest.""" 45 | 46 | 47 | class PandaResultError(PandaOperationalError): 48 | """Panda result server error.""" 49 | 50 | 51 | class PandaDisableModule(PandaOperationalError): 52 | """Exception for disabling a module dynamically.""" 53 | 54 | 55 | class PandaFeedbackError(PandaOperationalError): 56 | """Error in feedback module.""" 57 | 58 | 59 | class PandaApiError(PandaOperationalError): 60 | """Error during API usage.""" 61 | -------------------------------------------------------------------------------- /lib/implements/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # ================================================== 4 | # @Time : 2019-06-20 11:11 5 | # @Author : ryuchen 6 | # @File : __init__.py.py 7 | # @Desc : 8 | # ================================================== -------------------------------------------------------------------------------- /lib/implements/celery_app.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # ================================================== 4 | # @Time : 2019-06-20 14:40 5 | # @Author : ryuchen 6 | # @File : celery_app.py 7 | # @Desc : 8 | # ================================================== 9 | from celery.bin import worker 10 | 11 | from sandbox.celery import app 12 | from lib.base.Application import Application 13 | 14 | 15 | class CeleryApplication(Application): 16 | 17 | def __init__(self): 18 | super(CeleryApplication, self).__init__() 19 | 20 | def run(self) -> None: 21 | application = worker.worker(app=app) 22 | 23 | options = { 24 | 'loglevel': 'INFO', 25 | 'traceback': True, 26 | } 27 | 28 | application.run(**options) 29 | -------------------------------------------------------------------------------- /lib/implements/flask_app.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # ================================================== 4 | # @Time : 2019-06-20 14:40 5 | # @Author : ryuchen 6 | # @File : flask_app.py 7 | # @Desc : 8 | # ================================================== 9 | from apps.apis.router import app 10 | 11 | from lib.base.Application import Application 12 | 13 | 14 | class FlaskApplication(Application): 15 | 16 | def __init__(self) -> None: 17 | super(FlaskApplication, self).__init__() 18 | 19 | def run(self) -> None: 20 | app.run(port=8000, threaded=True) 21 | -------------------------------------------------------------------------------- /lib/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # ================================================== 4 | # @Time : 2019-06-20 14:17 5 | # @Author : ryuchen 6 | # @File : main.py 7 | # @Desc : 8 | # ================================================== 9 | -------------------------------------------------------------------------------- /lib/objects/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # ================================================== 4 | # @Time : 2019-06-20 23:01 5 | # @Author : ryuchen 6 | # @File : __init__.py.py 7 | # @Desc : 8 | # ================================================== -------------------------------------------------------------------------------- /lib/settings.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # ======================================================== 4 | # @Author: Ryuchen 5 | # @Time: 2019/05/30-19:30 6 | # @Site: https://ryuchen.github.io 7 | # @Contact: chenhaom1993@hotmail.com 8 | # @Copyright: Copyright (C) 2019-2020 Panda-Sandbox. 9 | # ======================================================== 10 | """ 11 | This to load the custom.yaml and merge into the default.yaml 12 | At same time, to check type of each setting 13 | """ 14 | 15 | import os 16 | import json 17 | import yaml 18 | 19 | from lib.defines.types import Int 20 | from lib.defines.types import String 21 | from lib.defines.context import SANDBOX_CONFIG_DIR 22 | 23 | 24 | class Variable(object): 25 | hostname = String(default="default", allow_empty=True) 26 | hostaddr = String(default="192.168.0.1", allow_empty=True) 27 | 28 | 29 | class Advanced(object): 30 | mode = Int(default=1, allow_empty=False, v_range=(1, 2)) 31 | 32 | 33 | class Settings(object): 34 | """ 35 | This function to protect the custom setting config does not influence the program successfully start up. 36 | """ 37 | # The default program settings 38 | default_path = os.path.join(SANDBOX_CONFIG_DIR, "default.yaml") 39 | 40 | # The finally running settings 41 | version = String(default="v1.0.0-alpha", allow_empty=False) 42 | variable = Variable() 43 | advanced = Advanced() 44 | 45 | @classmethod 46 | def loading_settings(cls): 47 | """ 48 | To merge the settings of default.yaml & the settings of custom.yaml into the running setting. 49 | :return: 50 | """ 51 | def merge_dict(target, source): 52 | for key, value in source.items(): 53 | if isinstance(value, dict): 54 | merge_dict(target.__dict__[key], value) 55 | else: 56 | setattr(target, key, value) 57 | 58 | if os.path.exists(cls.default_path): 59 | with open(cls.default_path) as default_config: 60 | cls.default_setting = yaml.load(default_config, Loader=yaml.SafeLoader) 61 | print(json.dumps(cls.default_setting, indent=4)) 62 | 63 | # # TODO: where to place the custom.yaml file 64 | # if os.path.exists(cls.default_path): 65 | # with open(cls.default_path) as default_config: 66 | # cls.settings = yaml.load(default_config, Loader=yaml.SafeLoader) 67 | 68 | if cls.default_setting: 69 | merge_dict(cls, cls.default_setting) 70 | 71 | return cls 72 | -------------------------------------------------------------------------------- /lib/utils/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # ================================================== 4 | # @Time : 2019-06-20 14:56 5 | # @Author : ryuchen 6 | # @File : __init__.py.py 7 | # @Desc : 8 | # ================================================== -------------------------------------------------------------------------------- /lib/utils/color.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # ================================================== 4 | # @Time : 2019-06-20 14:56 5 | # @Author : ryuchen 6 | # @File : color.py 7 | # @Desc : 8 | # ================================================== 9 | 10 | import os 11 | import sys 12 | 13 | 14 | def color(text, color_code): 15 | """Colorize text. 16 | @param text: text. 17 | @param color_code: color. 18 | @return: colorized text. 19 | """ 20 | # $TERM under Windows: 21 | # cmd.exe -> "" (what would you expect..?) 22 | # cygwin -> "cygwin" (should support colors, but doesn't work somehow) 23 | # mintty -> "xterm" (supports colors) 24 | if sys.platform == "win32" and os.getenv("TERM") != "xterm": 25 | return text 26 | return "\x1b[%dm%s\x1b[0m" % (color_code, text) 27 | 28 | 29 | def black(text): 30 | return color(text, 30) 31 | 32 | 33 | def red(text): 34 | return color(text, 31) 35 | 36 | 37 | def green(text): 38 | return color(text, 32) 39 | 40 | 41 | def yellow(text): 42 | return color(text, 33) 43 | 44 | 45 | def blue(text): 46 | return color(text, 34) 47 | 48 | 49 | def magenta(text): 50 | return color(text, 35) 51 | 52 | 53 | def cyan(text): 54 | return color(text, 36) 55 | 56 | 57 | def white(text): 58 | return color(text, 37) 59 | 60 | 61 | def bold(text): 62 | return color(text, 1) 63 | -------------------------------------------------------------------------------- /lib/utils/utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # ================================================== 4 | # @Time : 2019-06-20 22:30 5 | # @Author : ryuchen 6 | # @File : utils.py 7 | # @Desc : 8 | # ================================================== 9 | import threading 10 | 11 | 12 | class Singleton(type): 13 | """Singleton. 14 | @see: http://stackoverflow.com/questions/6760685/creating-a-singleton-in-python 15 | """ 16 | _instances = {} 17 | 18 | def __call__(cls, *args, **kwargs): 19 | if cls not in cls._instances: 20 | cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) 21 | return cls._instances[cls] 22 | 23 | 24 | class ThreadSingleton(type): 25 | """Singleton per thread.""" 26 | _instances = threading.local() 27 | 28 | def __call__(cls, *args, **kwargs): 29 | if not getattr(cls._instances, "instance", None): 30 | cls._instances.instance = super(ThreadSingleton, cls).__call__(*args, **kwargs) 31 | return cls._instances.instance 32 | -------------------------------------------------------------------------------- /sandbox/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # ================================================== 4 | # @Time : 2019-06-27 14:28 5 | # @Author : ryuchen 6 | # @File : __init__.py.py 7 | # @Desc : 8 | # ================================================== -------------------------------------------------------------------------------- /sandbox/celery.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # ================================================== 4 | # @Author : Copyright@Ryuchen 5 | # @License: MIT Licence 6 | # @File: test.py 7 | # @Time: 2019/06/20-10:23 8 | # @Contact: chenhaom1993@hotmail.com 9 | # @Site: https://ryuchen.github.io 10 | # ================================================== 11 | """ 12 | ... 13 | DocString Here 14 | ... 15 | """ 16 | import logging 17 | 18 | from celery import Celery 19 | from lib.settings import Settings 20 | 21 | log = logging.getLogger(__name__) 22 | 23 | # Once, as part of application setup, during deploy/migrations: 24 | # We need to setup the global default settings 25 | settings = Settings.loading_settings() 26 | 27 | # Initialise the celery redis connection 28 | redis_host = settings.get("connection", {}).get("redis", {}).get("host", "localhost") 29 | redis_port = settings.get("connection", {}).get("redis", {}).get("port", 6379) 30 | 31 | app = Celery( 32 | main='Panda-Sandbox', 33 | broker='redis://{0}:{1}/93'.format(redis_host, redis_port), 34 | backend='redis://{0}:{1}/77'.format(redis_host, redis_port) 35 | ) 36 | 37 | # Optional configuration, see the application user guide. 38 | # See more: https://docs.celeryproject.org/en/latest/userguide/configuration.html 39 | app.conf.update( 40 | task_serializer="json", 41 | result_serializer="json", 42 | accept_content=['json'], 43 | result_expires=3600, 44 | worker_concurrency=1, 45 | worker_max_tasks_per_child=1, 46 | worker_prefetch_multiplier=1, 47 | ) 48 | 49 | 50 | @app.on_configure.connect 51 | def setup_initialise_check(sender, **kwargs): 52 | print("app setup_initialise_check signals received") 53 | 54 | 55 | @app.on_after_configure.connect 56 | def setup_backend_service(sender, **kwargs): 57 | print("app setup_backend_service signals received") 58 | 59 | 60 | @app.on_after_finalize.connect 61 | def setup_finalize_check(sender, **kwargs): 62 | print("app setup_finalize_check signals received") 63 | -------------------------------------------------------------------------------- /scripts/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryuchen/Panda-Sandbox/09ae5da4b0ee4c688311208ce819dae82593490a/scripts/.gitkeep -------------------------------------------------------------------------------- /staff/android/anti-vm/fake-build.prop: -------------------------------------------------------------------------------- 1 | # begin build properties 2 | # autogenerated by buildinfo.sh 3 | ro.build.id=KTU84P 4 | ro.build.display.id=KTU84P 5 | ro.build.version.incremental=1227136 6 | ro.build.version.sdk=19 7 | ro.build.version.codename=REL 8 | ro.build.version.release=4.4.4 9 | ro.build.date=Fri Jun 13 07:05:49 UTC 2014 10 | ro.build.date.utc=1402643149 11 | ro.build.type=user 12 | ro.build.user=android-build 13 | ro.build.host=kpfj3.cbf.corp.google.com 14 | ro.build.tags=release-keys 15 | ro.product.model=Nexus 5 16 | ro.product.brand=google 17 | ro.product.name=hammerhead 18 | ro.product.device=hammerhead 19 | ro.product.board=hammerhead 20 | ro.product.cpu.abi=armeabi-v7a 21 | ro.product.cpu.abi2=armeabi 22 | ro.product.manufacturer=LGE 23 | ro.product.locale.language=en 24 | ro.product.locale.region=US 25 | ro.wifi.channels= 26 | ro.board.platform=msm8974 27 | # ro.build.product is obsolete; use ro.product.device 28 | ro.build.product=hammerhead 29 | # Do not try to parse ro.build.description or .fingerprint 30 | ro.build.description=hammerhead-user 4.4.4 KTU84P 1227136 release-keys 31 | ro.build.fingerprint=google/hammerhead/hammerhead:4.4.4/KTU84P/1227136:user/release-keys 32 | ro.build.characteristics=nosdcard 33 | # end build properties 34 | 35 | # 36 | # ADDITIONAL_BUILD_PROPERTIES 37 | # 38 | ro.config.ringtone=Titania.ogg 39 | ro.config.notification_sound=Tethys.ogg 40 | ro.config.alarm_alert=Oxygen.ogg 41 | ro.com.android.dateformat=MM-dd-yyyy 42 | ro.com.android.dataroaming=false 43 | ro.url.legal=http://www.google.com/intl/%s/mobile/android/basic/phone-legal.html 44 | ro.url.legal.android_privacy=http://www.google.com/intl/%s/mobile/android/basic/privacy.html 45 | ro.com.google.clientidbase=android-google 46 | ro.carrier=unknown 47 | ro.com.android.wifi-watchlist=GoogleGuest 48 | ro.error.receiver.system.apps=com.google.android.gms 49 | ro.setupwizard.enterprise_mode=1 50 | ro.opengles.version=196608 51 | ro.sf.lcd_density=480 52 | persist.hwc.mdpcomp.enable=true 53 | ro.hwui.texture_cache_size=72 54 | ro.hwui.layer_cache_size=48 55 | ro.hwui.r_buffer_cache_size=8 56 | ro.hwui.path_cache_size=32 57 | ro.hwui.gradient_cache_size=1 58 | ro.hwui.drop_shadow_cache_size=6 59 | ro.hwui.texture_cache_flushrate=0.4 60 | ro.hwui.text_small_cache_width=1024 61 | ro.hwui.text_small_cache_height=1024 62 | ro.hwui.text_large_cache_width=2048 63 | ro.hwui.text_large_cache_height=1024 64 | drm.service.enabled=true 65 | ro.qc.sensors.max_geomag_rotvec=60 66 | ro.qc.sensors.max_gyro_rate=200 67 | ro.qc.sensors.max_accel_rate=200 68 | ro.qc.sensors.max_grav=200 69 | ro.qc.sensors.max_rotvec=200 70 | ro.qc.sensors.max_ortn=200 71 | ro.qc.sensors.max_linacc=200 72 | ro.qc.sensors.max_gamerv_rate=200 73 | ro.qualcomm.sensors.smd=true 74 | ro.qualcomm.sensors.game_rv=true 75 | ro.qualcomm.sensors.georv=true 76 | ro.qc.sensors.smgr_mag_cal_en=true 77 | ro.qc.sensors.step_detector=true 78 | ro.qc.sensors.step_counter=true 79 | debug.qualcomm.sns.hal=w 80 | debug.qualcomm.sns.daemon=w 81 | debug.qualcomm.sns.libsensor1=w 82 | ro.telephony.call_ring.multiple=0 83 | wifi.interface=wlan0 84 | wifi.supplicant_scan_interval=15 85 | media.aac_51_output_enabled=true 86 | persist.radio.apm_sim_not_pwdn=1 87 | ro.telephony.default_network=10 88 | telephony.lteOnCdmaDevice=1 89 | persist.radio.mode_pref_nv10=1 90 | persist.audio.handset.mic.type=digital 91 | persist.audio.dualmic.config=endfire 92 | persist.audio.fluence.voicecall=true 93 | persist.audio.fluence.voicerec=false 94 | persist.audio.fluence.speaker=false 95 | af.resampler.quality=4 96 | persist.radio.custom_ecc=1 97 | persist.radio.always_send_plmn=true 98 | ro.input.noresample=1 99 | dalvik.vm.heapstartsize=8m 100 | dalvik.vm.heapgrowthlimit=192m 101 | dalvik.vm.heapsize=512m 102 | dalvik.vm.heaptargetutilization=0.75 103 | dalvik.vm.heapminfree=512k 104 | dalvik.vm.heapmaxfree=8m 105 | keyguard.no_require_sim=true 106 | ro.facelock.black_timeout=400 107 | ro.facelock.det_timeout=1500 108 | ro.facelock.rec_timeout=2500 109 | ro.facelock.lively_timeout=2500 110 | ro.facelock.est_max_time=600 111 | ro.facelock.use_intro_anim=false 112 | persist.sys.dalvik.vm.lib=libdvm.so 113 | dalvik.vm.dexopt-flags=m=y 114 | net.bt.name=Android 115 | dalvik.vm.stack-trace-file=/data/anr/traces.txt 116 | 117 | -------------------------------------------------------------------------------- /staff/android/anti-vm/fake-cpuinfo: -------------------------------------------------------------------------------- 1 | Processor : ARMv7 Processor rev 0 (v7l) 2 | BogoMIPS : 366.18 3 | Features : swp half thumb fastmult vfp edsp neon vfpv3 4 | CPU implementer : 0x41 5 | CPU architecture: 7 6 | CPU variant : 0x0 7 | CPU part : 0xc08 8 | CPU revision : 0 9 | 10 | Hardware : Qualcomm MSM 8974 HAMMERHEAD (Flattened Device Tree) 11 | Revision : 0000 12 | Serial : 0000000000000000 -------------------------------------------------------------------------------- /staff/android/anti-vm/fake-drivers: -------------------------------------------------------------------------------- 1 | /dev/tty /dev/tty 5 0 system:/dev/tty 2 | /dev/console /dev/console 5 1 system:console 3 | /dev/ptmx /dev/ptmx 5 2 system 4 | /dev/vc/0 /dev/vc/0 4 0 system:vtmaster 5 | rfcomm /dev/rfcomm 216 0-255 serial 6 | acm /dev/ttyACM 166 0-31 serial 7 | hso /dev/ttyHS 243 0-255 serial 8 | msm_serial_hsl /dev/ttyHSL 247 0-2 serial 9 | msm_serial_hs /dev/ttyHS 248 0-255 serial 10 | pty_slave /dev/pts 136 0-1048575 pty:slave 11 | pty_master /dev/ptm 128 0-1048575 pty:master 12 | unknown /dev/tty 4 1-63 console 13 | -------------------------------------------------------------------------------- /staff/android/apps/ImportContacts.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryuchen/Panda-Sandbox/09ae5da4b0ee4c688311208ce819dae82593490a/staff/android/apps/ImportContacts.apk -------------------------------------------------------------------------------- /staff/android/apps/Superuser.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryuchen/Panda-Sandbox/09ae5da4b0ee4c688311208ce819dae82593490a/staff/android/apps/Superuser.apk -------------------------------------------------------------------------------- /staff/android/apps/de.robv.android.xposed.installer_v33_36570c.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryuchen/Panda-Sandbox/09ae5da4b0ee4c688311208ce819dae82593490a/staff/android/apps/de.robv.android.xposed.installer_v33_36570c.apk -------------------------------------------------------------------------------- /staff/android/binaries/su: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryuchen/Panda-Sandbox/09ae5da4b0ee4c688311208ce819dae82593490a/staff/android/binaries/su -------------------------------------------------------------------------------- /staff/android/create_guest_avd.sh: -------------------------------------------------------------------------------- 1 | #/usr/bin/env bash 2 | 3 | #this script is meant for easy creation on an analysis machine for android emulator avd 4 | 5 | #Path to the local installation of the adb - android debug bridge utility. 6 | #ADB=/Applications/adt-bundle/sdk/platform-tools/adb 7 | 8 | ADB=$(which adb) 9 | 10 | if [ ! -f $ADB ] 11 | then 12 | echo "Error: adb path is not valid." 13 | exit 14 | fi 15 | echo "adb has been found." 16 | 17 | # First we root the emulator using Superuser 18 | echo "Pushing /system/xbin/su binary" 19 | $ADB remount 20 | $ADB push binaries/su /system/xbin/su 21 | $ADB shell chmod 06755 /system/xbin/su 22 | 23 | echo "Installing application Superuser" 24 | $ADB install apps/Superuser.apk 25 | 26 | # Install Xposed Application 27 | echo "Installing Xposed Application" 28 | $ADB install apps/de.robv.android.xposed.installer_v33_36570c.apk 29 | 30 | # Install Droidmon Application 31 | echo "Installing Droidmon Application" 32 | $ADB install hooking/Droidmon.apk 33 | 34 | # Install Anti Emulator Detection Application 35 | echo "Installing Anti Emulator Detection Application" 36 | $ADB install hooking/EmulatorAntiDetect.apk 37 | $ADB push anti-vm/fake-build.prop /data/local/tmp/ 38 | $ADB push anti-vm/fake-cpuinfo /data/local/tmp/ 39 | $ADB push anti-vm/fake-drivers /data/local/tmp/ 40 | 41 | # Install Content Generator 42 | echo "Installing Content Generator" 43 | $ADB install apps/ImportContacts.apk 44 | 45 | # Install Cuckoo Agent and Python for ARM 46 | echo "Installing Cuckoo Agent and Python for ARM" 47 | $ADB push ../../agent/android/python_agent/ /data/local/ 48 | $ADB shell chmod 06755 /data/local/aapt 49 | $ADB shell chmod 06755 /data/local/agent.sh 50 | $ADB shell chmod 06755 /data/local/python/bin/python 51 | 52 | echo "Device is ready!" 53 | -------------------------------------------------------------------------------- /staff/android/hooking/Droidmon.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryuchen/Panda-Sandbox/09ae5da4b0ee4c688311208ce819dae82593490a/staff/android/hooking/Droidmon.apk -------------------------------------------------------------------------------- /staff/android/hooking/EmulatorAntiDetect.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ryuchen/Panda-Sandbox/09ae5da4b0ee4c688311208ce819dae82593490a/staff/android/hooking/EmulatorAntiDetect.apk -------------------------------------------------------------------------------- /staff/darwin/bootstrap_guest.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Copyright (C) 2015 Dmitry Rodionov 3 | # This software may be modified and distributed under the terms 4 | # of the MIT license. See the LICENSE file for details. 5 | 6 | # Abstract 7 | # --------- 8 | # This is a bootstrap script for an OS X guest machine. It's able to: 9 | # 0) Update network settings (Ethernet) 10 | # 1) Install the anti-antitracing kernel module (aka `pt_deny_attach` kext) 11 | # 2) Patch /etc/sudoers to allow the user to launch `dtrace` and `date` 12 | # without a password 13 | # 3) Load and launch the Cuckoo guest agent (agent.py) 14 | # 15 | # Usage 16 | # --------- 17 | # The first two steps are optional, so by default this script will only download 18 | # and execute the arent.py. To install the kernel module or to patch the sudoers 19 | # file, use -k and -s flags respectively: 20 | # 21 | # ./bootstrap_guest.sh -k -- for loading the kext 22 | # ./bootstrap_guest.sh -s -- for patching /etc/sudoers 23 | # ./bootstrap_guest.sh -k -s -- for both actions 24 | # 25 | 26 | # Network settings 27 | IP_ADDRESS="192.168.56.101" 28 | SUBNET_MASK="255.255.255.0" 29 | ROUTER_ADDRESS="192.168.56.1" 30 | DNS_SERVERS=("208.67.220.220" "208.67.222.222") 31 | # Cuckoo agent locations 32 | AGENT_DIR="/Users/Shared" 33 | AGENT_URL="https://raw.githubusercontent.com/cuckoosandbox/cuckoo/master/agent/agent.py" 34 | 35 | opt_patch_sudoers=false; opt_install_kext=false; 36 | while getopts ":sk" opt; do 37 | case $opt in 38 | s) opt_patch_sudoers=true ;; 39 | k) opt_install_kext=true ;; 40 | \?) echo "Invalid option -$OPTARG" >&2 ;; 41 | esac 42 | done 43 | 44 | # [0] Setup network 45 | sudo networksetup -setmanual Ethernet $IP_ADDRESS $SUBNET_MASK $ROUTER_ADDRESS 46 | sudo networksetup -setdnsservers Ethernet "${DNS_SERVERS[@]}" 47 | 48 | # [1] Install `pt_deny_attach` kext. 49 | if [ "$opt_install_kext" == true ]; then 50 | # echo "[INFO]: Downloading 'pt_deny_attach' kext" 51 | # echo "[INFO]: Loading the kext into the kernel" 52 | # TODO(rodionovd): download and load the kext 53 | echo "[WARNING]: pt_deny_attach kext loading is not implemented yet." 54 | fi 55 | 56 | # [2] Patch /etc/sudoers to enable passwordless sudo for `dtrace` and `date` 57 | if [ "$opt_patch_sudoers" == true ]; then 58 | echo "[INFO]: Patching /etc/sudoers to enable passwordless dtrace for current user" 59 | user=$(whoami) 60 | if [ -z "$user" ]; then 61 | echo "[ERROR]: $(whoami) failed. /etc/sudoers wasn't patched." 62 | else 63 | # Since `>>` redirect is done by the shell itself and it drops all privileges, 64 | # we must run this command in a subshell. 65 | sudo sh -c "echo \"$user\tALL=(root) NOPASSWD: /usr/sbin/dtrace\" >> /etc/sudoers" 66 | sudo sh -c "echo \"$user\tALL=(root) NOPASSWD: /bin/date\" >> /etc/sudoers" 67 | fi 68 | fi 69 | 70 | # [3] Download agent.py into /Users/Shared 71 | echo "[INFO]: Downloading the Cuckoo guest agent" 72 | curl -o "$AGENT_DIR"/agent.py "$AGENT_URL" 73 | # [3.1] Install dependencies 74 | sudo easy_install pip 75 | (cd "$(dirname "$0")/.." && sudo -H pip install -r requirements.txt) 76 | # [4] and run it 77 | echo "[INFO]: Launching the Cuckoo guest agent" 78 | python "$AGENT_DIR"/agent.py 79 | -------------------------------------------------------------------------------- /staff/darwin/bootstrap_host.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Copyright (C) 2015 Dmitry Rodionov 3 | # This software may be modified and distributed under the terms 4 | # of the MIT license. See the LICENSE file for details. 5 | 6 | # Abstract 7 | # --------- 8 | # A bootstrap script for an OS X host. 9 | # 10 | # Usage 11 | # --------- 12 | # Launch this script with -i flag to create a host-only network interface 13 | # and assign it to the given VirtualBox guest machine: 14 | # ./bootstrap_host.sh -i MyOSXVirtualMachine 15 | # 16 | # Or just launch it without any arguments to enable traffic forwarding 17 | # for vboxnet0 host-only interface: 18 | # ./bootstrap_host.sh 19 | 20 | GUEST_IP="192.168.56.1" 21 | INTERFACE="vboxnet0" 22 | 23 | opt_create_interface=false; vmname=""; 24 | while getopts ":i:" opt; do 25 | case $opt in 26 | i) opt_create_interface=true; vmname="$OPTARG" ;; 27 | \?) echo "Invalid option -$OPTARG" >&2 ;; 28 | esac 29 | done 30 | 31 | # [1] Setup a host-only network interface (vboxnet0) 32 | if [ "$opt_create_interface" == true ]; then 33 | if [[ ! -f $(which vboxmanage) ]]; then 34 | echo -e "[Error] Could not locate vboxmanage. Please, install Virtual Box first." 35 | exit 1 36 | fi 37 | # Let's also verify that a VM with this name actually exists 38 | # Note: `vboxmanage list vms` outputs data in the following format: 39 | # "SandboxXP" {2b96015e-42e0-4662-b792-c738c2de155f} 40 | vm_exists=$(vboxmanage list vms | grep -c "\"$vmname\" {[0-9a-z\-]*}") 41 | if [ "$vm_exists" -ne 1 ]; then 42 | echo -e "[Error] Could not find a VM named \"$vmname\"." 43 | exit 1 44 | fi 45 | vboxmanage hostonlyif create 46 | # 192.168.56.1 is the default IP from `cuckoo.conf` 47 | vboxmanage hostonlyif ipconfig $INTERFACE --ip $GUEST_IP 48 | vboxmanage modifyvm "$vmname" --hostonlyadapter1 $INTERFACE 49 | vboxmanage modifyvm "$vmname" --nic1 hostonly 50 | fi 51 | 52 | # [2.1] Make sure vboxnet0 is up before doing anything with it 53 | vboxmanage hostonlyif ipconfig $INTERFACE --ip $GUEST_IP 54 | if [ "$(uname -s)" != "Darwin" ]; then 55 | echo "I can't setup traffic forwarding for your OS, sorry :(" 56 | else 57 | # [2.2] Enable traffic forwarding for vboxnet0 interface (for OS X only) 58 | sudo sysctl -w net.inet.ip.forwarding=1 &> /dev/null 59 | rules="nat on en1 from vboxnet0:network to any -> (en1) 60 | pass inet proto icmp all 61 | pass in on vboxnet0 proto udp from any to any port domain keep state 62 | pass quick on en1 proto udp from any to any port domain keep state" 63 | echo "$rules" > ./pfrules 64 | sudo pfctl -e -f ./pfrules 65 | rm -f ./pfrules 66 | fi 67 | -------------------------------------------------------------------------------- /staff/windows/create_guest_win7.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash -------------------------------------------------------------------------------- /staff/windows/qemu/android.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | machine_name 10 | machine_uuid 11 | 1048576 12 | 1048576 13 | 1 14 | 15 | hvm 16 | 17 | 18 | 19 | 20 | 21 | 22 | SandyBridge 23 | 24 | 25 | 26 | 27 | 28 | 29 | destroy 30 | restart 31 | destroy 32 | 33 | 34 | 35 | 36 | 37 | /usr/libexec/qemu-kvm 38 | 39 | 40 | 41 | 42 | 43 |
44 | 45 | 46 |
47 | 48 | 49 | 50 |
51 | 52 | 53 | 54 |
55 | 56 | 57 | 58 |
59 | 60 | 61 | 62 |
63 | 64 | 65 |
66 | 67 | 68 | 69 | 70 | 71 |
72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 |
82 | 83 | 84 | 85 |
86 | 87 | 88 | 89 | 90 | 91 | 92 |