virustotal_spec.rb 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. require 'spec_helper'
  2. load Metasploit::Framework.root.join('tools/exploit/virustotal.rb').to_path
  3. require 'msfenv'
  4. require 'digest/sha2'
  5. RSpec.describe VirusTotalUtility do
  6. context "Classes" do
  7. let(:api_key) do
  8. 'FAKE_API_KEY'
  9. end
  10. let(:filename) do
  11. 'MALWARE.EXE'
  12. end
  13. let(:malware_data) do
  14. 'DATA'
  15. end
  16. describe VirusTotalUtility::ToolConfig do
  17. context "Class methods" do
  18. let(:tool_config) do
  19. VirusTotalUtility::ToolConfig.new
  20. end
  21. context ".Initializer" do
  22. it "should init the config file path as Metasploit's default config path" do
  23. expect(tool_config.instance_variable_get(:@config_file)).to eq(Msf::Config.config_file)
  24. end
  25. it "should init the group name as 'VirusTotal'" do
  26. expect(tool_config.instance_variable_get(:@group_name)).to eq('VirusTotal')
  27. end
  28. end
  29. end
  30. end
  31. describe VirusTotalUtility::VirusTotal do
  32. context "Class methods" do
  33. let(:malware_sha256) do
  34. Digest::SHA256.hexdigest(malware_data)
  35. end
  36. let(:sample) do
  37. {
  38. 'filename' => filename,
  39. 'data' => malware_data,
  40. 'sha256' => malware_sha256
  41. }
  42. end
  43. let(:boundary) do
  44. 'THEREAREMANYLIKEITBUTTHISISMYDATA'
  45. end
  46. let(:scan_sample_opts) do
  47. opts = {
  48. 'boundary' => boundary,
  49. 'api_key' => api_key,
  50. 'filename' => filename,
  51. 'data' => malware_data
  52. }
  53. return opts
  54. end
  55. let(:retrieve_report_opts) do
  56. opts = {
  57. 'uri' => '/vtapi/v2/file/report',
  58. 'method' => 'POST',
  59. 'vhost' => 'www.virustotal.com',
  60. 'vars_post' => {
  61. 'apikey' => api_key,
  62. 'resource' => malware_sha256
  63. }
  64. }
  65. return opts
  66. end
  67. let(:vt) do
  68. file = double(File, read: malware_data)
  69. allow(File).to receive(:open).with(filename, 'rb') {|&block| block.yield file}
  70. VirusTotalUtility::VirusTotal.new({'api_key'=>api_key, 'sample'=>filename})
  71. end
  72. context ".Initializer" do
  73. it "should have an API key" do
  74. expect(vt.instance_variable_get(:@api_key)).to eq(api_key)
  75. end
  76. it "should have a checksum for the malware sample" do
  77. expect(vt.instance_variable_get(:@sample_info)['sha256']).to eq(malware_sha256)
  78. end
  79. end
  80. context "._load_sample" do
  81. it "should contain sample info including data, filename, and sha256" do
  82. expect(vt.send(:_load_sample, filename)).to eq(sample)
  83. end
  84. end
  85. context ".scan_sample" do
  86. it "should return with data" do
  87. expect(vt).to receive(:_execute_request).and_return('')
  88. expect(vt.scan_sample).to eq('')
  89. end
  90. end
  91. context ".retrieve_report" do
  92. it "should return with data" do
  93. expect(vt).to receive(:_execute_request).and_return('')
  94. expect(vt.retrieve_report).to eq('')
  95. end
  96. end
  97. context "._execute_request" do
  98. it "should return status code 204" do
  99. res = double(Rex::Proto::Http::Response)
  100. expect(res).to receive(:code).and_return(204)
  101. expect(vt).to receive(:send_request_cgi).with(scan_sample_opts).and_return(res)
  102. expect { vt.send(:_execute_request, scan_sample_opts) }.to raise_error(RuntimeError)
  103. end
  104. it "should return status code 403" do
  105. res = double(Rex::Proto::Http::Response)
  106. expect(res).to receive(:code).and_return(403)
  107. expect(vt).to receive(:send_request_cgi).with(scan_sample_opts).and_return(res)
  108. expect { vt.send(:_execute_request, scan_sample_opts) }.to raise_error(RuntimeError)
  109. end
  110. end
  111. context "._create_upload_data" do
  112. let(:form_opts) do
  113. {
  114. 'boundary' => boundary,
  115. 'api_key' => api_key,
  116. 'filename' => filename,
  117. 'data' => malware_data
  118. }
  119. end
  120. before(:example) do
  121. @upload_data = vt.send(:_create_upload_data, form_opts)
  122. end
  123. it "should create form-data with a boundary" do
  124. expect(@upload_data).to match(/#{boundary}/)
  125. end
  126. it "should create form-data with the API key" do
  127. expect(@upload_data).to match(/#{api_key}/)
  128. end
  129. it "should create form-data with the malware filename" do
  130. expect(@upload_data).to match(/#{filename}/)
  131. end
  132. it "should create form-data with the malware data" do
  133. expect(@upload_data).to match(/#{malware_data}/)
  134. end
  135. end
  136. end
  137. end
  138. describe VirusTotalUtility::Driver do
  139. before do
  140. $stdin = StringIO.new("Y\n")
  141. end
  142. after do
  143. $stdin = STDIN
  144. end
  145. let(:driver) do
  146. argv = "-k #{api_key} -f #{filename}".split
  147. options = {
  148. 'samples' => filename,
  149. 'api_key' => api_key,
  150. 'delay' => 60
  151. }
  152. expect(VirusTotalUtility::OptsConsole).to receive(:parse).with(anything).and_return(options)
  153. tool_config = instance_double(
  154. VirusTotalUtility::ToolConfig,
  155. has_privacy_waiver?: true,
  156. load_api_key: api_key,
  157. save_api_key: nil,
  158. save_privacy_waiver: nil
  159. )
  160. expect(VirusTotalUtility::ToolConfig).to receive(:new).and_return(tool_config)
  161. d = nil
  162. get_stdout {
  163. d = VirusTotalUtility::Driver.new
  164. }
  165. d
  166. end
  167. context ".Class methods" do
  168. context ".initialize" do
  169. it "should return a Driver object" do
  170. expect(driver.class).to eq(VirusTotalUtility::Driver)
  171. end
  172. end
  173. context ".ask_privacy" do
  174. it "should have a link of VirusTotal's terms of service" do
  175. tos = 'https://www.virustotal.com/en/about/terms-of-service'
  176. out = get_stdout { driver.ack_privacy }
  177. expect(out).to match(/#{tos}/)
  178. end
  179. end
  180. context ".generate_report" do
  181. it "should show a report" do
  182. res = {
  183. "scans" => {
  184. "Bkav" => { "detected" => false, "version" => "1.3.0.4613", "result" => nil, "update" => "20140107" }
  185. },
  186. "response_code" => 1
  187. }
  188. out = get_stdout { driver.generate_report(res, filename) }
  189. expect(out).to match(/#{res['scans']['Bkav']['version']}/)
  190. end
  191. end
  192. end
  193. end
  194. end
  195. end