├── .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 |
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 | |
|
|
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 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
--------------------------------------------------------------------------------
/staff/windows/qemu/default.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 | default
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/staff/windows/qemu/linux.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 | linux
10 | machine_uuid
11 | 1048576
12 | 1048576
13 | 1
14 |
15 | hvm
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | SandyBridge
24 |
25 |
26 |
27 |
28 |
29 |
30 | destroy
31 | restart
32 | destroy
33 |
34 |
35 |
36 |
37 |
38 | /usr/libexec/qemu-kvm
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 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
--------------------------------------------------------------------------------
/staff/windows/qemu/windows.xml:
--------------------------------------------------------------------------------
1 |
2 | machine_name
3 | machine_uuid
4 | 1048576
5 | 1048576
6 | 1
7 |
8 | /machine
9 |
10 |
11 | hvm
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | destroy
27 | restart
28 | destroy
29 |
30 |
31 |
32 |
33 |
34 | /usr/libexec/qemu-kvm
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # ==================================================
4 | # @Time : 2019-05-27 23:57
5 | # @Author : ryuchen
6 | # @Site :
7 | # @File : __init__.py.py
8 | # @Desc :
9 | # ==================================================
--------------------------------------------------------------------------------
/tests/unittest_agent.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # ========================================================
4 | # @Author: Ryuchen
5 | # @Time: 2019/12/05-13:24
6 | # @Site: https://ryuchen.github.io
7 | # @Contact: chenhaom1993@hotmail.com
8 | # @Copyright: Copyright (C) 2019-2020 Panda-Sandbox.
9 | # ========================================================
10 | """
11 | Using this unittest to test with agent.py function
12 | The function below was assemble from GuestManager
13 |
14 | Running this unittest, before you should starting agent on your develop system.
15 | """
16 | import os
17 | import sys
18 | import json
19 | import logging
20 | import requests
21 |
22 | sys.path.append(os.path.join(os.path.abspath(os.path.dirname(__file__)), ".."))
23 |
24 | from lib.exceptions.operation import PandaGuestError
25 |
26 | log = logging.getLogger(__name__)
27 |
28 |
29 | class FakeMachine(object):
30 | def __init__(self):
31 | self.label = 'test.vm1'
32 | self.addr = '196.168.56.4'
33 | self.port = 8554
34 |
35 |
36 | class AgentUnitTest(object):
37 | def __init__(self):
38 | self.machine = FakeMachine()
39 |
40 | def get(self, method, **kwargs):
41 | """Simple wrapper around requests.get()."""
42 | do_raise = kwargs.pop("do_raise", True)
43 | url = "http://%s:%s%s" % (self.machine.addr, self.machine.port, method)
44 | session = requests.Session()
45 | session.trust_env = False
46 | session.proxies = None
47 |
48 | try:
49 | r = session.get(url, **kwargs)
50 | except requests.ConnectionError:
51 | raise PandaGuestError(
52 | "Cuckoo Agent failed without error status, please try "
53 | "upgrading to the latest version of agent.py (>= 0.8) and "
54 | "notify us if the issue persists."
55 | )
56 |
57 | do_raise and r.raise_for_status()
58 | return r
59 |
60 | def post(self, method, *args, **kwargs):
61 | """Simple wrapper around requests.post()."""
62 | url = "http://%s:%s%s" % (self.machine.addr, self.machine.port, method)
63 | session = requests.Session()
64 | session.trust_env = False
65 | session.proxies = None
66 |
67 | try:
68 | r = session.post(url, *args, **kwargs)
69 | except requests.ConnectionError:
70 | raise PandaGuestError(
71 | "Cuckoo Agent failed without error status, please try "
72 | "upgrading to the latest version of agent.py (>= 0.8) and "
73 | "notify us if the issue persists."
74 | )
75 |
76 | r.raise_for_status()
77 | return r
78 |
79 |
80 | if __name__ == '__main__':
81 | # Check whether this is the new Agent or the old one (by looking at
82 | # the status code of the index page).
83 | agent_unit_test = AgentUnitTest()
84 | r = agent_unit_test.get("/", do_raise=False)
85 |
86 | if r.status_code != 200:
87 | log.critical(
88 | "While trying to determine the Agent version that your VM is "
89 | "running we retrieved an unexpected HTTP status code: %s. If "
90 | "this is a false positive, please report this issue to the "
91 | "Cuckoo Developers. HTTP response headers: %s",
92 | r.status_code, json.dumps(dict(r.headers)),
93 | )
94 | sys.exit()
95 |
96 | try:
97 | status = r.json()
98 | version = status.get("version")
99 | features = status.get("features", [])
100 | except Exception:
101 | log.critical(
102 | "We were unable to detect either the Old or New Agent in the "
103 | "Guest VM, are you sure you have set it up correctly? Please "
104 | "go through the documentation once more and otherwise inform "
105 | "the Cuckoo Developers of your issue."
106 | )
107 | sys.exit()
108 |
109 | log.critical(
110 | "Guest is running Cuckoo Agent %s (id=%s, ip=%s)",
111 | version, agent_unit_test.machine.label, agent_unit_test.machine.addr
112 | )
113 |
114 | # Pin the Agent to our IP address so that it is not accessible by
115 | # other Virtual Machines etc.
116 | if "pinning" in features:
117 | agent_unit_test.get("/pinning")
118 |
119 | print(features)
120 |
--------------------------------------------------------------------------------
/tests/unittest_setting.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # ========================================================
4 | # @Author: Ryuchen
5 | # @Time: 2019/12/05-13:41
6 | # @Site: https://ryuchen.github.io
7 | # @Contact: chenhaom1993@hotmail.com
8 | # @Copyright: Copyright (C) 2019-2020 Panda-Sandbox.
9 | # ========================================================
10 | """
11 | Using this unittest to test with settings.py function
12 | The function below was test with error configuration to change into correct
13 | """
14 | import os
15 | import sys
16 | import logging
17 |
18 | sys.path.append(os.path.join(os.path.abspath(os.path.dirname(__file__)), ".."))
19 |
20 | from lib.settings import Settings
21 |
22 | log = logging.getLogger(__name__)
23 |
24 |
25 | class SettingUnitTest(object):
26 | def __init__(self):
27 | # Once, as part of application setup, during deploy/migrations:
28 | # We need to setup the global default settings
29 | self.settings = Settings.loading_settings()
30 |
31 |
32 | if __name__ == '__main__':
33 | setting_unit_test = SettingUnitTest()
34 | print(setting_unit_test.settings.variable.hostname)
35 | print(setting_unit_test.settings.advanced.mode)
36 | print(setting_unit_test.settings.advanced.master)
37 |
--------------------------------------------------------------------------------
/tests/unittest_virtualbox.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # ==================================================
4 | # @Time : 2019-06-05 10:15
5 | # @Author : ryuchen
6 | # @File : unittest_vmware.py
7 | # @Desc :
8 | # ==================================================
9 | """
10 | Using this unittest to test vmware guest operate function
11 | The function below was assemble from Machinery
12 | """
13 | import os
14 | import sys
15 | import time
16 | import subprocess
17 |
18 | sys.path.append(os.path.join(os.path.abspath(os.path.dirname(__file__)), ".."))
19 |
20 |
21 | class VMwareUnitTest(object):
22 | def __init__(self, vmx_path):
23 | self.mode = "gui"
24 | self.vmware = {
25 | "path": "vmrun"
26 | }
27 | self.vmx_path = vmx_path
28 |
29 | def check_snapshot(self, snapshot):
30 | """Checks snapshot existance.
31 | @param snapshot: snapshot name
32 | @raise CuckooMachineError: if snapshot not found
33 | """
34 | try:
35 | p = subprocess.Popen([self.vmware.get("path"), "-T", "ws", "listSnapshots", self.vmx_path],
36 | stdout=subprocess.PIPE,
37 | stderr=subprocess.PIPE)
38 | output, _ = p.communicate()
39 | output = output.decode("utf-8")
40 | except OSError as e:
41 | print("Unable to get snapshot list for %s. Reason: %s" % (self.vmx_path, e))
42 | return False
43 | else:
44 | if output:
45 | output_lines = output.splitlines()
46 | if snapshot in output_lines:
47 | print("Found the snapshots name is %s" % snapshot)
48 | return True
49 | else:
50 | print("Doesn't has the correct snapshot setting")
51 | return False
52 | else:
53 | print("Unable to get snapshot list for %s. No output from `vmrun listSnapshots`" % self.vmx_path)
54 | return False
55 |
56 | def revert_to_snapshot(self, snapshot):
57 | """Revert machine to snapshot.
58 | @param snapshot: snapshot name
59 | @raise CuckooMachineError: if unable to revert
60 | """
61 | print("Revert snapshot for vm %s" % self.vmx_path)
62 | try:
63 | if subprocess.call([self.vmware.get("path"), "-T", "ws", "revertToSnapshot", self.vmx_path, snapshot],
64 | stdout=subprocess.PIPE,
65 | stderr=subprocess.PIPE):
66 | print("Unable to revert snapshot for machine %s: vmrun exited with error" % self.vmx_path)
67 | except OSError as e:
68 | print("Unable to revert snapshot for machine %s: %s" % (self.vmx_path, e))
69 |
70 | def _is_running(self):
71 | """Checks if virtual machine is running.
72 | @return: running status
73 | """
74 | try:
75 | p = subprocess.Popen([self.vmware.get("path"), "-T", "ws", "list"],
76 | stdout=subprocess.PIPE,
77 | stderr=subprocess.PIPE)
78 | output, error = p.communicate()
79 | output = output.decode("utf-8")
80 | except OSError as e:
81 | print("Unable to check running status for %s. Reason: %s" % (self.vmx_path, e))
82 | else:
83 | if output:
84 | output_lines = output.splitlines()
85 | print(output_lines)
86 | if self.vmx_path in output_lines:
87 | print("Found the snapshots name is %s" % self.vmx_path)
88 | return True
89 | else:
90 | print("Doesn't has the correct snapshot setting")
91 | return False
92 | else:
93 | return False
94 |
95 | def start_machine(self, snapshot):
96 |
97 | self.revert_to_snapshot(snapshot)
98 |
99 | print("Starting vm %s" % self.vmx_path)
100 | try:
101 | p = subprocess.Popen([self.vmware.get("path"), "-T", "ws", "start", self.vmx_path, self.mode],
102 | stdout=subprocess.PIPE,
103 | stderr=subprocess.PIPE)
104 | if self.mode.lower() == "gui":
105 | output, _ = p.communicate()
106 | output = output.decode("utf-8")
107 | if output:
108 | print("Unable to start machine %s: %s" % (self.vmx_path, output))
109 | except OSError as e:
110 | print("Unable to start machine %s in %s mode: %s" % (self.vmx_path, self.mode.upper(), e))
111 |
112 | def stop_machine(self):
113 | print("Stopping vm %s" % self.vmx_path)
114 | if self._is_running():
115 | try:
116 | if subprocess.call([self.vmware.get("path"), "-T", "ws", "stop", self.vmx_path, "hard"],
117 | stdout=subprocess.PIPE,
118 | stderr=subprocess.PIPE):
119 | print("Error shutting down machine %s" % self.vmx_path)
120 | except OSError as e:
121 | print("Error shutting down machine %s: %s" % (self.vmx_path, e))
122 | else:
123 | print("Trying to stop an already stopped machine: %s", self.vmx_path)
124 |
125 | def create_snapshot(self, snapshot):
126 | print("Starting vm %s" % self.vmx_path)
127 | try:
128 | p = subprocess.Popen([self.vmware.get("path"), "-T", "ws", "start", self.vmx_path, self.mode],
129 | stdout=subprocess.PIPE,
130 | stderr=subprocess.PIPE)
131 | if self.mode.lower() == "gui":
132 | output, _ = p.communicate()
133 | output = output.decode("utf-8")
134 | if output:
135 | print("Unable to start machine %s: %s" % (self.vmx_path, output))
136 | except OSError as e:
137 | print("Unable to start machine %s in %s mode: %s" % (self.vmx_path, self.mode.upper(), e))
138 |
139 | print("Create vm %s snapshot" % self.vmx_path)
140 | try:
141 | p = subprocess.Popen([self.vmware.get("path"), "-T", "ws", "snapshot", self.vmx_path, snapshot],
142 | stdout=subprocess.PIPE,
143 | stderr=subprocess.PIPE)
144 | output, _ = p.communicate()
145 | output = output.decode("utf-8")
146 | if output:
147 | print("Create snapshot with machine %s: %s" % (self.vmx_path, output))
148 | except OSError as e:
149 | print("Unable to start machine %s in %s mode: %s" % (self.vmx_path, self.mode.upper(), e))
150 |
151 |
152 | if __name__ == '__main__':
153 | vm = VMwareUnitTest(vmx_path="/Users/ryuchen/Virtual Machines.localized/Windows 7.vmwarevm/Windows 7.vmx")
154 | if vm.check_snapshot("snapshot-1"):
155 | vm.start_machine("snapshot-1")
156 |
157 | time.sleep(10)
158 |
159 | vm.stop_machine()
160 | else:
161 | vm.create_snapshot("snapshot-1")
162 | vm.start_machine("snapshot-1")
163 |
164 | time.sleep(10)
165 |
166 | vm.stop_machine()
167 |
168 |
--------------------------------------------------------------------------------