Have you ever had an amazing idea for automating two or more pieces of technology and then realized one of them doesn’t have an API?

I came across this problem more than once during the development of a couple of projects here at Praetorian. In this post, I’ll share some of the libraries and techniques I have used to build out APIs for CLI programs, such as HashCat and nmap. Hopefully, these techniques and libraries will be helpful to you when building out new web applications and frameworks.

The first project I worked on at Praetorian was a tool called Project Mars (formerly PWAudit). The goal of the project was to leverage cloud-based GPU systems to crack password hashes while offering users a clean, powerful interface. We decided to use HashCat for the backend hash-cracking tool, and we needed to come up with a way to communicate with multiple cracking sessions for the service to work.

Download HashCat API on GitHub →

Interfacing with CLI Programs

While researching various ways to interface with CLI programs, I stumbled upon a ruby gem called ruby-nmap. After looking through the source code, I noticed it was using a library, called RProgram, to wrap the nmap binary. RProgram was built to give developers a simple and powerful way to run command line programs from within ruby. It basically allows you to map out any flags as methods to an object. Mapping an entire program is relatively quick, and RProgram includes additional options for multiple arguments per flag, trailing equal signs, and trailing arguments. One thing to note is that arguments are ordered by assignment, so if your program is sensitive to the order of certain flags, make sure you assign the variables in the order you would normally. Here is a small example of how RProgram works:

Define a flag:

		short_option :flag => '-o', :name => :outfile	

Run it:

		prog.run do |p|     p.outfile = 'test.txt'end	

Result:

		./prog -o test.txt	

Queueing Asynchronous Tasks

RProgram is a blocking process and runs as long as the CLI program is processing. In order to interact with log files and not block up the API, I use a gem called SuckerPunch to handle asynchronous tasking. SuckerPunch is a pretty simple gem that makes queueing asynchronous tasks very easy. Here is an example of using SuckerPunch:

		class Tool     def run(async=false)          if async               Async.new.async.run(self)          else               sleep 15               $stdout.write "This is Async"               sleep 15          end     endendclass Async     include ::SuckerPunch::Job     workers 3     def run(obj)          obj.run     endendtool = Tool.newtool.run(true)puts "hello"puts "test"sleep 40	

This outputs:

		hellotestThis is Async	

SuckerPunch allows me to parse output and log files while the CLI program is running in the background. I always create a .pid file in the method that is called async in order to know when the program is running and when the program has finished. One thing to note when using SuckerPunch is that the parent process must remain open/running in order to finish all the async tasks. This works great inside Sinatra web applications, since they are always running. I also use Sinatra to write my REST APIs. It is an amazing framework for deploying web applications quickly with minimal code. You can literally deploy a Sinatra app in four lines of code.

As for SuckerPunch, I have used it in multiple web applications to queue tasks such as emailing and generating files. I usually write a class file to make the entire process an object. It allows me to validate input for flags and keep the code nice and organized. You can view an example in the repo that I have posted along with this post.

Constructing the REST API

Now that you have your wrapper and your program is non-blocking, we just have to create the REST API. When I build REST APIs for CLI programs, they usually consist of three main routes: /start.json, /status.json, and /results.json. Depending on your needs and the features of the program you are interfacing with, you can add additional routes such as /stop.json, /resume.json, /delete.json, etc. Here is an example of a simple status route:

		get '/status.json' do  content_type :json  if File.exists?('prog.pid')    return {'status' => 'running'}.to_json  else    return {'status' => 'complete'}.to_json  endend	

Sinatra has a very small footprint, so it requires very little to get spun up.

Challenges with HashCat’s Output

While developing the Ruby HashCat API, I ran into somewhat of a problem while creating the wrapper. Unfortunately, HashCat does not output a complete status file, and some statistics are only shown via STDOUT. I would have preferred some form of a log file that held all the statistics because it is a pain to parse STDOUT, but in this case I had no other choice. I submitted a feature request to the developers to address this, though my request was denied. HashCat does have a feature to replace the verbose status output with a simplified, machine-readable line, although it still outputs via STDOUT. I wrote a parser for both methods, and it works.

Download HashCat API on GitHub →

Currently, the HashCat API supports:

  • Starting a new crack job
  • Checking the status of a crack job
  • Grabbing the results of a crack job
  • Cleaning all files from a crack job