mido.sh 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763
  1. #!/bin/sh
  2. # Copyright (C) 2024 Elliot Killick <contact@elliotkillick.com>
  3. # Licensed under the MIT License. See LICENSE file for details.
  4. [ "$DEBUG" ] && set -x
  5. # Prefer Dash shell for greater security if available
  6. if [ "$BASH" ] && command -v dash > /dev/null; then
  7. exec dash "$0" "$@"
  8. fi
  9. # Test for 4-bit color (16 colors)
  10. # Operand "colors" is undefined by POSIX
  11. # If the operand doesn't exist, the terminal probably doesn't support color and the program will continue normally without it
  12. if [ "0$(tput colors 2> /dev/null)" -ge 16 ]; then
  13. RED='\033[0;31m'
  14. BLUE='\033[0;34m'
  15. GREEN='\033[0;32m'
  16. NC='\033[0m'
  17. fi
  18. # Avoid printing messages as potential terminal escape sequences
  19. echo_ok() { printf "%b%s%b" "${GREEN}[+]${NC} " "$1" "\n" >&2; }
  20. echo_info() { printf "%b%s%b" "${BLUE}[i]${NC} " "$1" "\n" >&2; }
  21. echo_err() { printf "%b%s%b" "${RED}[!]${NC} " "$1" "\n" >&2; }
  22. # https://pubs.opengroup.org/onlinepubs/9699919799/utilities/fold.html
  23. format() { fold -s; }
  24. word_count() { echo $#; }
  25. usage() {
  26. echo "Mido - The Secure Microsoft Windows Downloader"
  27. echo ""
  28. echo "Usage: $0 <windows_media>..."
  29. echo ""
  30. echo "Download specified list of Windows media."
  31. echo ""
  32. echo "Specify \"all\", or one or more of the following Windows media:"
  33. echo " win7x64-ultimate"
  34. echo " win81x64"
  35. echo " win10x64"
  36. echo " win11x64"
  37. echo " win81x64-enterprise-eval"
  38. echo " win10x64-enterprise-eval"
  39. echo " win11x64-enterprise-eval"
  40. echo " win10x64-enterprise-ltsc-eval (most secure)"
  41. echo " win2008r2"
  42. echo " win2012r2-eval"
  43. echo " win2016-eval"
  44. echo " win2019-eval"
  45. echo " win2022-eval"
  46. echo ""
  47. echo "Each ISO download takes between 3 - 7 GiBs (average: 5 GiBs)."
  48. echo ""
  49. echo "Updates"
  50. echo "-------"
  51. echo "All the downloads provided here are the most up-to-date releases that Microsoft provides. This is ensured by programmatically checking Microsoft's official download pages to get the latest download link. In other cases, the Windows version in question is no longer supported by Microsoft meaning a direct download link (stored in Mido) will always point to the most up-to-date release." | format
  52. echo ""
  53. echo "Remember to update Windows to the latest patch level after installation."
  54. echo ""
  55. echo "Overuse"
  56. echo "-------"
  57. echo "Newer consumer versions of Windows including win81x64, win10x64, and win11x64 are downloaded through Microsoft's gated download web interface. Do not overuse this interface. Microsoft may be quick to do ~24 hour IP address bans after only a few download requests (especially if they are done in quick succession). Being temporarily banned from one of these downloads (e.g. win11x64) doesn't cause you to be banned from any of the other downloads provided through this interface." | format
  58. echo ""
  59. echo "Privacy Preserving Technologies"
  60. echo "-------------------------------"
  61. echo "The aforementioned Microsoft gated download web interface is currently blocking Tor (and similar technologies). They say this is to prevent people in restricted regions from downloading certain Windows media they shouldn't have access to. This is fine by most standards because Tor is too slow for large downloads anyway and we have checksum verification for security." | format
  62. echo ""
  63. echo "Language"
  64. echo "--------"
  65. echo "All the downloads provided here are for English (United States). This helps to great simplify maintenance and minimize the user's fingerprint. If another language is desired then that can easily be configured in Windows once it's installed." | format
  66. echo ""
  67. echo "Architecture"
  68. echo "------------"
  69. echo "All the downloads provided here are for x86-64 (x64). This is the only architecture Microsoft ships Windows Server in.$([ -d /run/qubes ] && echo ' Also, the only architecture Qubes OS supports.')" | format
  70. }
  71. # Media naming scheme info:
  72. # Windows Server has no architecture because Microsoft only supports amd64 for this version of Windows (the last version to support x86 was Windows Server 2008 without the R2)
  73. # "eval" is short for "evaluation", it's simply the license type included with the Windows installation (only exists on enterprise/server) and must be specified in the associated answer file
  74. # "win7x64" has the "ultimate" edition appended to it because it isn't "multi-edition" like the other Windows ISOs (for multi-edition ISOs the edition is specified in the associated answer file)
  75. readonly win7x64_ultimate="win7x64-ultimate.iso"
  76. readonly win81x64="win81x64.iso"
  77. readonly win10x64="win10x64.iso"
  78. readonly win11x64="win11x64.iso"
  79. readonly win81x64_enterprise_eval="win81x64-enterprise-eval.iso"
  80. readonly win10x64_enterprise_eval="win10x64-enterprise-eval.iso"
  81. readonly win11x64_enterprise_eval="win11x64-enterprise-eval.iso"
  82. readonly win10x64_enterprise_ltsc_eval="win10x64-enterprise-ltsc-eval.iso"
  83. readonly win2008r2="win2008r2.iso"
  84. readonly win2012r2_eval="win2012r2-eval.iso"
  85. readonly win2016_eval="win2016-eval.iso"
  86. readonly win2019_eval="win2019-eval.iso"
  87. readonly win2022_eval="win2022-eval.iso"
  88. parse_args() {
  89. for arg in "$@"; do
  90. if [ "$arg" = "-h" ] || [ "$arg" = "--help" ]; then
  91. usage
  92. exit
  93. fi
  94. done
  95. if [ $# -lt 1 ]; then
  96. usage >&2
  97. exit 1
  98. fi
  99. # Append to media_list so media is downloaded in the order they're passed in
  100. for arg in "$@"; do
  101. case "$arg" in
  102. win7x64-ultimate)
  103. media_list="$media_list $win7x64_ultimate"
  104. ;;
  105. win81x64)
  106. media_list="$media_list $win81x64"
  107. ;;
  108. win10x64)
  109. media_list="$media_list $win10x64"
  110. ;;
  111. win11x64)
  112. media_list="$media_list $win11x64"
  113. ;;
  114. win81x64-enterprise-eval)
  115. media_list="$media_list $win81x64_enterprise_eval"
  116. ;;
  117. win10x64-enterprise-eval)
  118. media_list="$media_list $win10x64_enterprise_eval"
  119. ;;
  120. win11x64-enterprise-eval)
  121. media_list="$media_list $win11x64_enterprise_eval"
  122. ;;
  123. win10x64-enterprise-ltsc-eval)
  124. media_list="$media_list $win10x64_enterprise_ltsc_eval"
  125. ;;
  126. win2008r2)
  127. media_list="$media_list $win2008r2"
  128. ;;
  129. win2012r2-eval)
  130. media_list="$media_list $win2012r2_eval"
  131. ;;
  132. win2016-eval)
  133. media_list="$media_list $win2016_eval"
  134. ;;
  135. win2019-eval)
  136. media_list="$media_list $win2019_eval"
  137. ;;
  138. win2022-eval)
  139. media_list="$media_list $win2022_eval"
  140. ;;
  141. all)
  142. media_list="$win7x64_ultimate $win81x64 $win10x64 $win11x64 $win81x64_enterprise_eval $win10x64_enterprise_eval $win11x64_enterprise_eval $win10x64_enterprise_ltsc_eval $win2008r2 $win2012r2_eval $win2016_eval $win2019_eval $win2022_eval"
  143. break
  144. ;;
  145. *)
  146. echo_err "Invalid Windows media specified: $arg"
  147. exit 1
  148. ;;
  149. esac
  150. done
  151. }
  152. handle_curl_error() {
  153. error_code="$1"
  154. fatal_error_action=2
  155. case "$error_code" in
  156. 6)
  157. echo_err "Failed to resolve Microsoft servers! Is there an Internet connection? Exiting..."
  158. return "$fatal_error_action"
  159. ;;
  160. 7)
  161. echo_err "Failed to contact Microsoft servers! Is there an Internet connection or is the server down?"
  162. ;;
  163. 8)
  164. echo_err "Microsoft servers returned a malformed HTTP response!"
  165. ;;
  166. 22)
  167. echo_err "Microsoft servers returned a failing HTTP status code!"
  168. ;;
  169. 23)
  170. echo_err "Failed at writing Windows media to disk! Out of disk space or permission error? Exiting..."
  171. return "$fatal_error_action"
  172. ;;
  173. 26)
  174. echo_err "Ran out of memory during download! Exiting..."
  175. return "$fatal_error_action"
  176. ;;
  177. 36)
  178. echo_err "Failed to continue earlier download!"
  179. ;;
  180. 63)
  181. echo_err "Microsoft servers returned an unexpectedly large response!"
  182. ;;
  183. # POSIX defines exit statuses 1-125 as usable by us
  184. # https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_08_02
  185. $((error_code <= 125)))
  186. # Must be some other server or network error (possibly with this specific request/file)
  187. # This is when accounting for all possible errors in the curl manual assuming a correctly formed curl command and an HTTP(S) request, using only the curl features we're using, and a sane build
  188. echo_err "Miscellaneous server or network error!"
  189. ;;
  190. 126 | 127)
  191. echo_err "Curl command not found! Please install curl and try again. Exiting..."
  192. return "$fatal_error_action"
  193. ;;
  194. # Exit statuses are undefined by POSIX beyond this point
  195. *)
  196. case "$(kill -l "$error_code")" in
  197. # Signals defined to exist by POSIX:
  198. # https://pubs.opengroup.org/onlinepubs/009695399/basedefs/signal.h.html
  199. INT)
  200. echo_err "Curl was interrupted!"
  201. ;;
  202. # There could be other signals but these are most common
  203. SEGV | ABRT)
  204. echo_err "Curl crashed! Failed exploitation attempt? Please report any core dumps to curl developers. Exiting..."
  205. return "$fatal_error_action"
  206. ;;
  207. *)
  208. echo_err "Curl terminated due to a fatal signal!"
  209. ;;
  210. esac
  211. esac
  212. return 1
  213. }
  214. part_ext=".PART"
  215. unverified_ext=".UNVERIFIED"
  216. scurl_file() {
  217. out_file="$1"
  218. tls_version="$2"
  219. url="$3"
  220. part_file="${out_file}${part_ext}"
  221. # --location: Microsoft likes to change which endpoint these downloads are stored on but is usually kind enough to add redirects
  222. # --fail: Return an error on server errors where the HTTP response code is 400 or greater
  223. curl --progress-bar --location --output "$part_file" --continue-at - --max-filesize 10G --fail --proto =https "--tlsv$tls_version" --http1.1 -- "$url" || {
  224. error_code=$?
  225. handle_curl_error "$error_code"
  226. error_action=$?
  227. # Clean up and make sure a future resume doesn't happen from a bad download resume file
  228. if [ -f "$out_file" ]; then
  229. # If file is empty, bad HTTP code, or bad download resume file
  230. if [ ! -s "$out_file" ] || [ "$error_code" = 22 ] || [ "$error_code" = 36 ]; then
  231. echo_info "Deleting failed download..."
  232. rm -f "$out_file"
  233. fi
  234. fi
  235. return "$error_action"
  236. }
  237. # Full downloaded succeeded, ready for verification check
  238. mv "$part_file" "${out_file}"
  239. }
  240. manual_verification() {
  241. media_verification_failed_list="$1"
  242. checksum_verification_failed_list="$2"
  243. echo_info "Manual verification instructions"
  244. echo " 1. Get checksum (may already be done for you):" >&2
  245. echo " sha256sum <ISO_FILENAME>" >&2
  246. echo "" >&2
  247. echo " 2. Verify media:" >&2
  248. echo " Web search: https://duckduckgo.com/?q=%22CHECKSUM_HERE%22" >&2
  249. echo " Onion search: https://duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad.onion/?q=%22CHECKSUM_HERE%22" >&2
  250. echo " \"No results found\" or unexpected results indicates the media has been modified and should not be used." >&2
  251. echo "" >&2
  252. echo " 3. Remove the $unverified_ext extension from the file after performing or deciding to skip verification (not recommended):" >&2
  253. echo " mv <ISO_FILENAME>$unverified_ext <ISO_FILENAME>" >&2
  254. echo "" >&2
  255. for media in $media_verification_failed_list; do
  256. # Read current checksum in list and then read remaining checksums back into the list (effectively running "shift" on the variable)
  257. # POSIX sh doesn't support indexing so do this instead to iterate both lists at once
  258. # POSIX sh doesn't support here-strings (<<<). We could also use the "cut" program but that's not a builtin
  259. IFS=' ' read -r checksum checksum_verification_failed_list << EOF
  260. $checksum_verification_failed_list
  261. EOF
  262. echo " ${media}${unverified_ext} = $checksum" >&2
  263. echo " Web search: https://duckduckgo.com/?q=%22$checksum%22" >&2
  264. echo " Onion search: https://duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad.onion/?q=%22$checksum%22" >&2
  265. echo " mv ${media}${unverified_ext} $media" >&2
  266. echo "" >&2
  267. done
  268. echo " Theses searches can be performed in a web/Tor browser or more securely using" >&2
  269. echo " ddgr (Debian/Fedora packages available) terminal search tool if preferred." >&2
  270. echo " Once validated, consider updating the checksums in Mido by submitting a pull request on GitHub." >&2
  271. # If you're looking for a single secondary source to cross-reference checksums then try here: https://files.rg-adguard.net/search
  272. # This site is recommended by the creator of Rufus in the Fido README and has worked well for me
  273. }
  274. consumer_download() {
  275. # Copyright (C) 2024 Elliot Killick <contact@elliotkillick.com>
  276. # Licensed under the MIT License. See LICENSE file for details.
  277. #
  278. # This function is from the Mido project:
  279. # https://github.com/ElliotKillick/Mido
  280. # Download newer consumer Windows versions from behind gated Microsoft API
  281. out_file="$1"
  282. # Either 8, 10, or 11
  283. windows_version="$2"
  284. url="https://www.microsoft.com/en-us/software-download/windows$windows_version"
  285. case "$windows_version" in
  286. 8 | 10) url="${url}ISO" ;;
  287. esac
  288. user_agent="Mozilla/5.0 (X11; Linux x86_64; rv:100.0) Gecko/20100101 Firefox/100.0"
  289. # uuidgen: For MacOS (installed by default) and other systems (e.g. with no /proc) that don't have a kernel interface for generating random UUIDs
  290. session_id="$(cat /proc/sys/kernel/random/uuid 2> /dev/null || uuidgen --random)"
  291. # Get product edition ID for latest release of given Windows version
  292. # Product edition ID: This specifies both the Windows release (e.g. 22H2) and edition ("multi-edition" is default, either Home/Pro/Edu/etc., we select "Pro" in the answer files) in one number
  293. # This is a request we make that Fido doesn't. Fido manually maintains a list of all the Windows release/edition product edition IDs in its script (see: $WindowsVersions array). This is helpful for downloading older releases (e.g. Windows 10 1909, 21H1, etc.) but we always want to get the newest release which is why we get this value dynamically
  294. # Also, keeping a "$WindowsVersions" array like Fido does would be way too much of a maintenance burden
  295. # Remove "Accept" header that curl sends by default (match Fido requests)
  296. iso_download_page_html="$(curl -sS --user-agent "$user_agent" --header "Accept:" --max-filesize 1M --fail --proto =https --tlsv1.2 --http1.1 -- "$url")" || {
  297. handle_curl_error $?
  298. return $?
  299. }
  300. # tr: Filter for only numerics to prevent HTTP parameter injection
  301. # head -c was recently added to POSIX: https://austingroupbugs.net/view.php?id=407
  302. product_edition_id="$(echo "$iso_download_page_html" | grep -Eo '<option value="[0-9]+">Windows' | cut -d '"' -f 2 | head -n 1 | tr -cd '0-9' | head -c 16)"
  303. [ "$VERBOSE" ] && echo "Product edition ID: $product_edition_id" >&2
  304. # Permit Session ID
  305. # "org_id" is always the same value
  306. curl -sS --output /dev/null --user-agent "$user_agent" --header "Accept:" --max-filesize 100K --fail --proto =https --tlsv1.2 --http1.1 -- "https://vlscppe.microsoft.com/tags?org_id=y6jn8c31&session_id=$session_id" || {
  307. # This should only happen if there's been some change to how this API works
  308. handle_curl_error $?
  309. return $?
  310. }
  311. # Extract everything after the last slash
  312. url_segment_parameter="${url##*/}"
  313. # Get language -> skuID association table
  314. # SKU ID: This specifies the language of the ISO. We always use "English (United States)", however, the SKU for this changes with each Windows release
  315. # We must make this request so our next one will be allowed
  316. # --data "" is required otherwise no "Content-Length" header will be sent causing HTTP response "411 Length Required"
  317. language_skuid_table_html="$(curl -sS --request POST --user-agent "$user_agent" --data "" --header "Accept:" --max-filesize 10K --fail --proto =https --tlsv1.2 --http1.1 -- "https://www.microsoft.com/en-US/api/controls/contentinclude/html?pageId=a8f8f489-4c7f-463a-9ca6-5cff94d8d041&host=www.microsoft.com&segments=software-download,$url_segment_parameter&query=&action=getskuinformationbyproductedition&sessionId=$session_id&productEditionId=$product_edition_id&sdVersion=2")" || {
  318. handle_curl_error $?
  319. return $?
  320. }
  321. # tr: Filter for only alphanumerics or "-" to prevent HTTP parameter injection
  322. sku_id="$(echo "$language_skuid_table_html" | grep "English (United States)" | sed 's/&quot;//g' | cut -d ',' -f 1 | cut -d ':' -f 2 | tr -cd '[:alnum:]-' | head -c 16)"
  323. [ "$VERBOSE" ] && echo "SKU ID: $sku_id" >&2
  324. # Get ISO download link
  325. # If any request is going to be blocked by Microsoft it's always this last one (the previous requests always seem to succeed)
  326. # --referer: Required by Microsoft servers to allow request
  327. iso_download_link_html="$(curl -sS --request POST --user-agent "$user_agent" --data "" --referer "$url" --header "Accept:" --max-filesize 100K --fail --proto =https --tlsv1.2 --http1.1 -- "https://www.microsoft.com/en-US/api/controls/contentinclude/html?pageId=6e2a1789-ef16-4f27-a296-74ef7ef5d96b&host=www.microsoft.com&segments=software-download,$url_segment_parameter&query=&action=GetProductDownloadLinksBySku&sessionId=$session_id&skuId=$sku_id&language=English&sdVersion=2")" || {
  328. # This should only happen if there's been some change to how this API works
  329. handle_curl_error $?
  330. return $?
  331. }
  332. if ! [ "$iso_download_link_html" ]; then
  333. # This should only happen if there's been some change to how this API works
  334. echo_err "Microsoft servers gave us an empty response to our request for an automated download."
  335. manual_verification="true"
  336. return 1
  337. fi
  338. if echo "$iso_download_link_html" | grep -q "We are unable to complete your request at this time."; then
  339. echo_err "Microsoft blocked the automated download request based on your IP address."
  340. manual_verification="true"
  341. return 1
  342. fi
  343. # Filter for 64-bit ISO download URL
  344. # sed: HTML decode "&" character
  345. # tr: Filter for only alphanumerics or punctuation
  346. iso_download_link="$(echo "$iso_download_link_html" | grep -o "https://software.download.prss.microsoft.com.*IsoX64" | cut -d '"' -f 1 | sed 's/&amp;/\&/g' | tr -cd '[:alnum:][:punct:]')"
  347. if ! [ "$iso_download_link" ]; then
  348. # This should only happen if there's been some change to the download endpoint web address
  349. echo_err "Microsoft servers gave us no download link to our request for an automated download."
  350. manual_verification="true"
  351. return 1
  352. fi
  353. #echo_ok "Got latest ISO download link (valid for 24 hours): $iso_download_link"
  354. # Download ISO
  355. scurl_file "$out_file" "1.3" "$iso_download_link"
  356. }
  357. enterprise_eval_download() {
  358. # Copyright (C) 2024 Elliot Killick <contact@elliotkillick.com>
  359. # Licensed under the MIT License. See LICENSE file for details.
  360. #
  361. # This function is from the Mido project:
  362. # https://github.com/ElliotKillick/Mido
  363. # Download enterprise evaluation Windows versions
  364. out_file="$1"
  365. windows_version="$2"
  366. enterprise_type="$3"
  367. url="https://www.microsoft.com/en-us/evalcenter/download-$windows_version"
  368. iso_download_page_html="$(curl -sS --location --max-filesize 1M --fail --proto =https --tlsv1.2 --http1.1 -- "$url")" || {
  369. handle_curl_error $?
  370. return $?
  371. }
  372. if ! [ "$iso_download_page_html" ]; then
  373. # This should only happen if there's been some change to where this download page is located
  374. echo_err "Windows enterprise evaluation download page gave us an empty response"
  375. return 1
  376. fi
  377. iso_download_links="$(echo "$iso_download_page_html" | grep -o "https://go.microsoft.com/fwlink/p/?LinkID=[0-9]\+&clcid=0x[0-9a-z]\+&culture=en-us&country=US")" || {
  378. # This should only happen if there's been some change to the download endpoint web address
  379. echo_err "Windows enterprise evaluation download page gave us no download link"
  380. return 1
  381. }
  382. # Limit untrusted size for input validation
  383. iso_download_links="$(echo "$iso_download_links" | head -c 1024)"
  384. case "$enterprise_type" in
  385. # Select x64 download link
  386. "enterprise") iso_download_link=$(echo "$iso_download_links" | head -n 2 | tail -n 1) ;;
  387. # Select x64 LTSC download link
  388. "ltsc") iso_download_link=$(echo "$iso_download_links" | head -n 4 | tail -n 1) ;;
  389. *) iso_download_link="$iso_download_links" ;;
  390. esac
  391. # Follow redirect so proceeding log message is useful
  392. # This is a request we make this Fido doesn't
  393. # We don't need to set "--max-filesize" here because this is a HEAD request and the output is to /dev/null anyway
  394. iso_download_link="$(curl -sS --location --output /dev/null --silent --write-out "%{url_effective}" --head --fail --proto =https --tlsv1.2 --http1.1 -- "$iso_download_link")" || {
  395. # This should only happen if the Microsoft servers are down
  396. handle_curl_error $?
  397. return $?
  398. }
  399. # Limit untrusted size for input validation
  400. iso_download_link="$(echo "$iso_download_link" | head -c 1024)"
  401. #echo_ok "Got latest ISO download link: $iso_download_link"
  402. # Use highest TLS version for endpoints that support it
  403. case "$iso_download_link" in
  404. "https://download.microsoft.com"*) tls_version="1.2" ;;
  405. *) tls_version="1.3" ;;
  406. esac
  407. # Download ISO
  408. scurl_file "$out_file" "$tls_version" "$iso_download_link"
  409. }
  410. download_media() {
  411. echo_info "Downloading Windows media from official Microsoft servers..."
  412. media_download_failed_list=""
  413. for media in $media_list; do
  414. case "$media" in
  415. "$win7x64_ultimate")
  416. echo_info "Downloading Windows 7..."
  417. # Source, Google search this (it can be found many places): "dec04cbd352b453e437b2fe9614b67f28f7c0b550d8351827bc1e9ef3f601389" "download.microsoft.com"
  418. # This Windows 7 ISO bundles MSU update packages
  419. # It's the most up-to-date Windows 7 ISO that Microsoft offers (August 2018 update): https://files.rg-adguard.net/files/cea4210a-3474-a17a-88d4-4b3e10bd9f66
  420. # Of particular interest to us is the update that adds support for SHA-256 driver signatures so Qubes Windows Tools installs correctly
  421. #
  422. # Microsoft purged Windows 7 from all their servers...
  423. # More info about this event: https://github.com/pbatard/Fido/issues/64
  424. # Luckily, the ISO is still available on the Wayback Machine so get the last copy of it from there
  425. # This is still secure because we validate with the checksum from before the purge
  426. # The only con then is that web.archive.org is a much slower download source than the Microsoft servers
  427. echo_info "Microsoft has unfortunately purged all downloads of Windows 7 from their servers so this identical download is sourced from: web.archive.org"
  428. scurl_file "$media" "1.3" "https://web.archive.org/web/20221228154140/https://download.microsoft.com/download/5/1/9/5195A765-3A41-4A72-87D8-200D897CBE21/7601.24214.180801-1700.win7sp1_ldr_escrow_CLIENT_ULTIMATE_x64FRE_en-us.iso"
  429. ;;
  430. "$win81x64")
  431. echo_info "Downloading Windows 8.1..."
  432. consumer_download "$media" 8
  433. ;;
  434. "$win10x64")
  435. echo_info "Downloading Windows 10..."
  436. consumer_download "$media" 10
  437. ;;
  438. "$win11x64")
  439. echo_info "Downloading Windows 11..."
  440. consumer_download "$media" 11
  441. ;;
  442. "$win81x64_enterprise_eval")
  443. echo_info "Downloading Windows 8.1 Enterprise Evaluation..."
  444. # This download link is "Update 1": https://files.rg-adguard.net/file/166cbcab-1647-53d5-1785-6ef9e22a6500
  445. # A more up-to-date "Update 3" enterprise ISO exists but it was only ever distributed by Microsoft through MSDN which means it's impossible to get a Microsoft download link now: https://files.rg-adguard.net/file/549a58f2-7813-3e77-df6c-50609bc6dd7c
  446. # win81x64 is "Update 3" but that's isn't an enterprise version (although technically it's possible to modify a few files in the ISO to get any edition)
  447. # If you want "Update 3" enterprise though (not from Microsoft servers), then you should still be able to get it from here: https://archive.org/details/en_windows_8.1_enterprise_with_update_x64_dvd_6054382_202110
  448. # "Update 1" enterprise also seems to be the ISO used by other projects
  449. # Old source, used to be here but Microsoft deleted it: http://technet.microsoft.com/en-us/evalcenter/hh699156.aspx
  450. # Source: https://gist.github.com/eyecatchup/11527136b23039a0066f
  451. scurl_file "$media" "1.2" "https://download.microsoft.com/download/B/9/9/B999286E-0A47-406D-8B3D-5B5AD7373A4A/9600.17050.WINBLUE_REFRESH.140317-1640_X64FRE_ENTERPRISE_EVAL_EN-US-IR3_CENA_X64FREE_EN-US_DV9.ISO"
  452. ;;
  453. "$win10x64_enterprise_eval")
  454. echo_info "Downloading Windows 10 Enterprise Evaluation..."
  455. enterprise_eval_download "$media" windows-10-enterprise enterprise
  456. ;;
  457. "$win11x64_enterprise_eval")
  458. echo_info "Downloading Windows 11 Enterprise Evaluation..."
  459. enterprise_eval_download "$media" windows-11-enterprise enterprise
  460. ;;
  461. "$win10x64_enterprise_ltsc_eval")
  462. echo_info "Downloading Windows 10 Enterprise LTSC Evaluation..."
  463. enterprise_eval_download "$media" windows-10-enterprise ltsc
  464. ;;
  465. "$win2008r2")
  466. echo_info "Downloading Windows Server 2008 R2..."
  467. # Old source, used to be here but Microsoft deleted it: https://www.microsoft.com/en-us/download/details.aspx?id=11093
  468. # Microsoft took down the original download link provided by that source too but this new one has the same checksum
  469. # Source: https://github.com/rapid7/metasploitable3/pull/563
  470. scurl_file "$media" "1.2" "https://download.microsoft.com/download/4/1/D/41DEA7E0-B30D-4012-A1E3-F24DC03BA1BB/7601.17514.101119-1850_x64fre_server_eval_en-us-GRMSXEVAL_EN_DVD.iso"
  471. ;;
  472. "$win2012r2_eval")
  473. echo_info "Downloading Windows Server 2012 R2 Evaluation..."
  474. enterprise_eval_download "$media" windows-server-2012-r2 server
  475. ;;
  476. "$win2016_eval")
  477. echo_info "Downloading Windows Server 2016 Evaluation..."
  478. enterprise_eval_download "$media" windows-server-2016 server
  479. ;;
  480. "$win2019_eval")
  481. echo_info "Downloading Windows Server 2019 Evaluation..."
  482. enterprise_eval_download "$media" windows-server-2019 server
  483. ;;
  484. "$win2022_eval")
  485. echo_info "Downloading Windows Server 2022 Evaluation..."
  486. enterprise_eval_download "$media" windows-server-2022 server
  487. ;;
  488. esac || {
  489. error_action=$?
  490. media_download_failed_list="$media_download_failed_list $media"
  491. # Return immediately on a fatal error action
  492. if [ "$error_action" = 2 ]; then
  493. return
  494. fi
  495. }
  496. done
  497. }
  498. verify_media() {
  499. # SHA256SUMS file
  500. # Some of these Windows ISOs are EOL (e.g. win81x64) so their checksums will always match
  501. # For all other Windows ISOs, a new release will make their checksums no longer match
  502. #
  503. # IMPORTANT: These checksums are not necessarily subject to being updated
  504. # Unfortunately, the maintenance burden would be too large and even if I did there would still be some time gap between Microsoft releasing a new ISO and me updating the checksum (also, users would have to update this script)
  505. # For these reasons, I've opted for a slightly more manual verification where you have to look up the checksum to see if it's a well-known Windows ISO checksum
  506. # Ultimately, you have to trust Microsoft because they could still include a backdoor in the verified ISO (keeping Windows air gapped could help with this)
  507. # Community contributions for these checksums are welcome
  508. #
  509. # Leading backslash is to avoid prepending a newline while maintaining alignment
  510. readonly sha256sums="\
  511. dec04cbd352b453e437b2fe9614b67f28f7c0b550d8351827bc1e9ef3f601389 win7x64-ultimate.iso
  512. d8333cf427eb3318ff6ab755eb1dd9d433f0e2ae43745312c1cd23e83ca1ce51 win81x64.iso
  513. # Windows 10 22H2
  514. a6f470ca6d331eb353b815c043e327a347f594f37ff525f17764738fe812852e win10x64.iso
  515. # Windows 11 23H2 v2
  516. 36de5ecb7a0daa58dce68c03b9465a543ed0f5498aa8ae60ab45fb7c8c4ae402 win11x64.iso
  517. 2dedd44c45646c74efc5a028f65336027e14a56f76686a4631cf94ffe37c72f2 win81x64-enterprise-eval.iso
  518. ef7312733a9f5d7d51cfa04ac497671995674ca5e1058d5164d6028f0938d668 win10x64-enterprise-eval.iso
  519. ebbc79106715f44f5020f77bd90721b17c5a877cbc15a3535b99155493a1bb3f win11x64-enterprise-eval.iso
  520. e4ab2e3535be5748252a8d5d57539a6e59be8d6726345ee10e7afd2cb89fefb5 win10x64-enterprise-ltsc-eval.iso
  521. 30832ad76ccfa4ce48ccb936edefe02079d42fb1da32201bf9e3a880c8ed6312 win2008r2.iso
  522. 6612b5b1f53e845aacdf96e974bb119a3d9b4dcb5b82e65804ab7e534dc7b4d5 win2012r2-eval.iso
  523. 1ce702a578a3cb1ac3d14873980838590f06d5b7101c5daaccbac9d73f1fb50f win2016-eval.iso
  524. 6dae072e7f78f4ccab74a45341de0d6e2d45c39be25f1f5920a2ab4f51d7bcbb win2019-eval.iso
  525. 3e4fa6d8507b554856fc9ca6079cc402df11a8b79344871669f0251535255325 win2022-eval.iso"
  526. # Read sha256sums line-by-line to build known checksum and media lists
  527. # Only use shell builtins for better security and stability
  528. # Don't use a for loop because IFS cannot temporarily be set using that
  529. while IFS="$(printf '\n')" read -r line; do
  530. # Ignore comments and empty lines
  531. case "$line" in
  532. "#"* | "") continue ;;
  533. esac
  534. # Read first and second words of line
  535. IFS=' ' read -r known_checksum known_media _ << EOF
  536. $line
  537. EOF
  538. known_checksum_list="$known_checksum_list $known_checksum"
  539. known_media_list="$known_media_list $known_media"
  540. done << EOF
  541. $sha256sums
  542. EOF
  543. media_verification_failed_list=""
  544. checksum_verification_failed_list=""
  545. for media in $media_list; do
  546. # Scan for unverified media files
  547. if ! [ -f "${media}${unverified_ext}" ]; then
  548. continue
  549. fi
  550. if [ "$verify_media_message_shown" != "true" ]; then
  551. echo_info "Verifying integrity..."
  552. verify_media_message_shown="true"
  553. fi
  554. checksum_line="$(sha256sum "${media}${unverified_ext}")"
  555. # Get first word of checksum line
  556. IFS=' ' read -r checksum _ << EOF
  557. $checksum_line
  558. EOF
  559. # Sanity check: Assert correct size of SHA-256 checksum
  560. if [ ${#checksum} != 64 ]; then
  561. echo_err "Failed SHA-256 sanity check! Exiting..."
  562. exit 2
  563. fi
  564. known_checksum_list_iterator="$known_checksum_list"
  565. # Search known media and checksum lists for the current media
  566. for known_media in $known_media_list; do
  567. IFS=' ' read -r known_checksum known_checksum_list_iterator << EOF
  568. $known_checksum_list_iterator
  569. EOF
  570. if [ "$media" = "$known_media" ]; then
  571. break
  572. fi
  573. done
  574. # Verify current media integrity
  575. if [ "$checksum" = "$known_checksum" ]; then
  576. echo "$media: OK"
  577. mv "${media}${unverified_ext}" "$media"
  578. else
  579. echo "$media: UNVERIFIED"
  580. media_verification_failed_list="$media_verification_failed_list $media"
  581. checksum_verification_failed_list="$checksum_verification_failed_list $checksum"
  582. fi
  583. # Reset known checksum list iterator so we can iterate on it again for the next media
  584. known_checksum_list_iterator="$known_checksum_list"
  585. done
  586. }
  587. ending_summary() {
  588. echo "" >&2
  589. if [ "$media_download_failed_list" ]; then
  590. for media in $media_download_failed_list; do
  591. media_download_failed_argument_list="$media_download_failed_argument_list ${media%%.iso}"
  592. done
  593. fi
  594. # Exit codes
  595. # 0: Success
  596. # 1: Argument parsing error
  597. # 2: Runtime error (see error message for more info)
  598. # 3: One or more downloads failed
  599. # 4: One or more verifications failed
  600. # 5: At least one download and one verification failed (when more than one media is specified)
  601. exit_code=0
  602. # Determine exit code
  603. if [ "$media_download_failed_list" ] && [ "$media_verification_failed_list" ]; then
  604. exit_code=5
  605. else
  606. if [ "$media_download_failed_list" ]; then
  607. exit_code=3
  608. elif [ "$media_verification_failed_list" ]; then
  609. exit_code=4
  610. fi
  611. fi
  612. trap -- - EXIT
  613. if [ "$exit_code" = 0 ]; then
  614. echo_ok "Successfully downloaded Windows image!"
  615. else
  616. echo_ok "Finished! Please see the above errors with information"
  617. exit "$exit_code"
  618. fi
  619. }
  620. # https://unix.stackexchange.com/questions/752570/why-does-trap-passthough-zero-instead-of-the-signal-the-process-was-killed-wit
  621. handle_exit() {
  622. exit_code=$?
  623. signal="$1"
  624. if [ "$exit_code" != 0 ] || [ "$signal" ]; then
  625. echo "" >&2
  626. echo_err "Mido was exited abruptly!"
  627. fi
  628. if [ "$exit_code" != 0 ]; then
  629. trap -- - EXIT
  630. exit "$exit_code"
  631. elif [ "$signal" ]; then
  632. trap -- - "$signal"
  633. kill -s "$signal" -- $$
  634. fi
  635. }
  636. # Enable exiting on error
  637. #
  638. # Disable shell globbing
  639. # This isn't necessary given that all unquoted variables (e.g. for determining word count) are set directly by us but it's just a precaution
  640. set -ef
  641. # IFS defaults to many different kinds of whitespace but we only care about space
  642. # Note: This means that ISO filenames cannot contain spaces but that's a bad idea anyway
  643. IFS=' '
  644. parse_args "$@"
  645. # POSIX sh doesn't include signals in its EXIT trap so do it ourselves
  646. signo=1
  647. while true; do
  648. # "kill" is a shell builtin
  649. # shellcheck disable=SC2064
  650. case "$(kill -l "$signo" 2> /dev/null)" in
  651. # Trap on all catchable terminating signals as defined by POSIX
  652. # Stop (i.e. suspend) signals (like Ctrl + Z or TSTP) are fine because they can be resumed
  653. # Most signals result in termination so this way is easiest (Linux signal(7) only adds more terminating signals)
  654. #
  655. # https://pubs.opengroup.org/onlinepubs/009695399/basedefs/signal.h.html
  656. # https://unix.stackexchange.com/a/490816
  657. # Signal WINCH was recently added to POSIX: https://austingroupbugs.net/view.php?id=249
  658. CHLD | CONT | URG | WINCH | KILL | STOP | TSTP | TTIN | TTOU) ;;
  659. *) trap "handle_exit $signo" "$signo" 2> /dev/null || break ;;
  660. esac
  661. signo=$((signo + 1))
  662. done
  663. trap handle_exit EXIT
  664. download_media
  665. verify_media
  666. ending_summary