Objective 10: Recover Cleartext Document
This objective asks us to decrypt an encrypted document. Holly Evergreen gives the hint to solve this one. Here is the link that she gives. https://www.youtube.com/watch?v=obJdpKDpFBA&feature=youtu.be
At first, this one seems like it would be extremely difficult because it has a five tree out of five tree difficulty rating. I think that that mostly comes from our heads though. We think something will be difficult, so it is difficult. If you watch Ron Bowes’ talk, Reverse Engineering, The Easy Way, you see hints in there that makes it not so bad. It's just a way of thinking that most people aren't accustomed to. First step, download the software and use it like a person normally would - encrypt and decrypt a file. Get a feel for how it works. A notable feature is that this encryption software sends a key to a 3rd party that stores the key (AKA key escrow). Then the user is given a guid in which they can use to retrieve the key. One thing that sticks out is that it has an option that is not recommended, which is to send the traffic over HTTP with the insecure option. So, one could use something like Wireshark or tcpdump to record the traffic and see it. This is good for a reverse engineer or someone troubleshooting an issue because they can better understand how it works. This is not good for every day use because it can show the key being sent as well as give clues about how to retrieve or reverse engineer keys without sniffing traffic. https://en.wikipedia.org/wiki/Key_escrow
Notice, in the following image, that when the insecure flag is used that the key is sent in clear text, and the GUID is sent back to the user in cleartext as well, making this insecure flag dangerous. Not only does this give attackers this one key, but it gives clues to the inner working of the application as well.
Already we learn a little bit about the application. The key is 16 hexadecimal characters long. 1 hexadecimal character is 4 bits (AKA a nibble). 2 hexadecimal characters is 4 + 4 = 8 bits. A byte is 8 bits. 16 hexadecimal digits/2 hexadecimal digits per byte = 8 bytes. So, the key is 8 bytes long. Usually DES has an 8 byte key. So, we can hypothesize that the encryption is DES. Also, look at the seed. It looks like a UNIX timestamp. Interesting that in the question, they mention a timeframe that they believe it was encrypted.
After normal functionality has been observed, including watching the insecure version in Wireshark, use a program like IDA to examine the elfscrow.exe binary. It's fairly easy to use. Just open it, Click on New, Choose your executable, click open. Usually the default information is ok. In the challenge description, we are given debugging symbols that may be helpful. If you have IDA, the elfscrow binary, and the debugging symbols in the same directory, and you try to load the executable into IDA Pro, it will ask you if you want to load the debugging symbols with it as well. Click on yes.
This program, like basically any other program has a main function. I don't have an extensive knowledge of all the terminology involved in programming, however I do remember a couple of things. The main function can call other functions. Code execution isn't necessary linear. when you’re reading assembly or other code. The application will start in main, go to the first line, and go in sequential order in main, however, it may have to jump to other functions. So you can't always read the code top to bottom. When it calls another function, you'll see something like call 0x1234. That means you need to find the function at address 0x1234 to see what that does. When it's done with 0x1234, it'll jump back to the main function after the call statement and follow in sequential order until it comes across another function, jump, or loop. Keep in mind, the other functions can call other functions, have jumps, or loops as well. But it's the same idea. It goes in sequential order unless it sees something like a jump or a loop… Thankfully, we don’t have to be assembly gurus to solve this challenge. Ron says it's the easy way. Note, even if we don't understand all of it, we can still see the insecure flag I mentioned earlier. So we can read some of it. That's enough to make educated guesses about what is going on. If that fails, then we can Google anything we don't know.
On the left hand side, you should be able to see a listing for the functions. If you don’t, it might be because the View is set to a different setting than normal. Go to View, Open Subviews, then choose Functions to see them. Notice the interesting functions? “super_secure_srand” which is expecting to get an integer (probably the seed), then “super_secure_random"(which expects nothing), “generate_key", and “time". Also, note the store key are retrieve key which are the web functions that we saw earlier when we ran the application. Double Clicking on any of those functions “jumps" to that function in the Disassembly View Again, if you don’t see the Disassembly, you probably need to adjust the View. Same directions as getting the Functions, except, choosing the Disassembly view option instead. Below, the super secure function is shown.
The most interesting thing to note is how the key is generated, so we'll look at that first. The most important thing to note here is that the time function is called to get the current time. Then it's pushed onto the stack and is used as the seed. The seed is then sent to super_secure_srand. See how that function needs an integer (int)? The integer given to it is the seed.
Logically the next place to look would be super_secure_srand. The srand function is used to set the starting point for producing a series of pseudo-random integers. If the application didn’t call this, it would be as it started at one, so that may may it predictable. So, the application is starting it at whatever the current time is. https://www.geeksforgeeks.org/rand-and-srand-in-ccpp/ Not really much of a reason to look at the assembly on srand, because we now know what it does. Only thing interesting about this one is that it calls super_secure_random. This is where things get interesting. Note that the values below are in hex, however, they can be converted to decimal by right clicking on them and selecting the base10 option. It is the icon on the menu with a number 10 in it. There is an H next to the number to show it's currently in HEX. The image of the right is converted to base10. Those are the constants that Ron mentions in his video that we need to lookup.
If you Google those constants, you find that it's the constants for the Linear Congruential Generator that creates the random numbers for the key. Considering that Ron has a solution skeleton outlining a potential way to decrypt files that only needs an encryption algorithm and the random number generator that is written in Ruby in the github page that he released with the video, https://tinyurl.com/kringlecon-crypto, it might be wise to grab the ruby version of this Linear Congruential Generator from here: https://rosettacode.org/wiki/Linear_congruential_generator.
Below is Ron's solution skeleton. Notice that it says exactly what to put where. Here is the link: https://github.com/CounterHack/reversing-crypto-talk-public/blob/master/demoes/demo7%20-%20putting%20it%20all%20together/demo7%20-%20solution%20skeleton.rb The code is continued on the next page. I added comments to hopefully make it more clear.
require 'openssl'
#
key length? We know it's 8 bytes from executing the program and looking at the key length.KEY_LENGTH = 8
#A function that generates the key (pseudo random number) if it's given a seed as a starting point.def generate_key(seed)
key = “"
#the following is a loop that creates each character of the key. So basically it says, from character one, all the way up to the ending character (the 8th character),
#generate a random number.1.upto(KEY_LENGTH) do
#Below we have to generate the random number with the PRNG - this is where we add the code with the constants we Googled.key += (seed & 0x0FF).chr
end
#Return the full 8 byte key to the calling functionreturn key
end
#This function decrypts the data using the key.def decrypt(data, key)
#What cipher is it? We already know. DES.
c = OpenSSL::Cipher::ALG.new(ALG_DETAILS)
c.decrypt
c.key = key
#He mentioned in his talk about the pattern of the blocks of each cipher. One hint people may miss is that padding may be necessary. It should go here.
#Return the decrypted data. Note, he’s returning a string, not a file. We'll have to figure out how to decrypt a file.return (c.update(data) + c.final())
end
#This function shows how to use this ruby script. Give it data, and a seed and it'll decrypt the data into a string.if(!ARGV[1])
puts("Usage: ruby ./solution.rb <hex data> <seed>")
exit
end
#Tells ruby to expect HEX Datadata = [ARGV[0]].pack('H*')
#Tells ruby to convert the string that the user types to an integer. The key function is expecting an integer.seed = ARGV[1].to_i
#Call the generate_key function, give it the seed, so it will generate a key and return it.key = generate_key(seed)
#Display the key to the screenputs("Generated key: #{key.unpack(‘H*’)}")
#Display the decrypted data as a string.puts "Decrypted -> " + decrypt(data, key)
This is how I solved it. Note, I re-purposed some of Ron's code. I didn't know Ruby, well, here's one way to do it: Reusing code and Googling. I found how to decrypt DES in Ruby in these articles. https://stackoverflow.com/questions/26493253/des-decryption-in-ruby https://stackoverflow.com/questions/56741153/how-do-i-decrypt-files-that-are-encrypted-using-des-command-in-ruby I narrowed the time period down by looking at the creation date of the (elfscrow) encryption executable. Obviously you can't encrypt the file before the encryption software was made. So, that narrowed it down to between 8:20:50 PM UTC and 9:00 PM UTC. Secondly, I generated the seeds using Powershell and pulled them in at the top of this code into an array. I used a loop to try each seed and create a file that is potentially decrypted. There are more efficient ways. There were quite a few files to sift through. Thankfully file -i /path/to/files/* | grep “application/pdf” will bring up the decrypted file.
require 'openssl'
#Read the file that I created containing the seeds into an arrayarr = []
file_lines = File.readlines(“~/Desktop/narrowseeds.txt")
file_lines.each do |line|
arr << line.strip
end
#Key Length is 8 - it's DES - Figured this out from key length.KEY_LENGTH = 8
#Generate the Key with the Linear Congruential Generator code, because that's how the program does it.def generate_key(seed)
key = ""
1.upto(KEY_LENGTH) do
key += ((((seed = 214013 * seed + 2531011) >> 16) & 0x7fff) &0x0FF).chr
end
return key
end
def decrypt(key,number)
#Algorithm is just plain DES. c = OpenSSL::Cipher::DES.new()
c.decrypt
c.key = key
#to figure out that the padding was needed, I Googled an error I was getting. Ron explains it in the video, but I missed it the first time watching the video. c.padding = 0
#Found this decryption routine by Googling how to decrypt files encrypted with DES in ruby file=File.open("/root/Desktop/decrypt_attempts/#{key.unpack('H*')}.pdf",’wb') do |outf|
decrypted = c.update(File.read(‘ElfUResearchLabsSuperSledOMaticQuickStartGuideV1.2.pdf.enc')) + c.final
outf.write(decrypted)
end
end
#show how to use this ruby programif(!ARGV[1])
puts("Usage: ruby ./solution.rb <hex data> <seed>")
exit
end
#initialize counternumber = 0
#For each seed in the array, do the followingfor seed in arr do
#Add one to the counter number = number + 1
#display the seed being tested puts(seed)
#generate the key from this seed key = generate_key(seed.to_i)
#display the potential key puts("Generated key: #{key.unpack(‘H*')}")
#I was going to use number to create different filenames with each key, but decided to use the hex instead, so I knew what the key was. Unfortunately the method I used printed
out [“"] as well for the file name. Linux didn’t care though, still saved them to disk. decrypt(key,number)
end
Here’s how I generated the seeds in Powershell. Note, I'm changing my local time to UTC.
#initialize the variable.$unixTime = $null
#if there's an error, keep going without bugging me about it. (Some errors will still show up, but they are less likely.)$ErrorActionPreference = “SilentlyContinue"
#initialize the seeds array$seeds = @()
#Nested loops to create timestamps for all the minutes and seconds. Didn't need an hour one because we narrowed it down to less than an hour. Could have been narrowed down more, but this is good enough. for($m=0; $m -lt 60; $m++){
for($s=0; $s -lt 60; $s++){
#This is taking into account if the numbers are less than 10 for both seconds and minutes.
if($s -lt 10 -and $m -lt 10){
#Get my local time, change it to UTC, then format it in unixTime format. Inner parentheses are resolved, first, then the next one out, and then the last. $unixTime = [long] (Get-Date -Date ((Get-Date "12/6/2019 14:0$m:0$s").ToUniversalTime()) -UFormat %s)
}
#This is taking into account if the numbers are less than 10 for seconds and greater than 9 for minutes. if($s -lt 10 -and $m -gt 9){
$unixTime = [long] (Get-Date -Date ((Get-Date "12/6/2019 14:$m:0$s").ToUniversalTime()) -UFormat %s)
}
#This is taking into account if the numbers are less than 10 for minutes and greater than 9 for seconds. if($s -gt 9 -and $m -lt 10){
$unixTime = [long] (Get-Date -Date ((Get-Date "12/6/2019 14:0$m`:$s").ToUniversalTime()) -UFormat %s)
}
else{
#if minutes and seconds are both greater than 10, do the following. $unixTime = [long] (Get-Date -Date ((Get-Date "12/6/2019 14`:$m`:$s").ToUniversalTime()) -UFormat %s)
}
#Add each timestamp to the array. $seeds += $unixTime
}
}
#Create a file containing all the seeds. Make it ASCII because Linux barfs on Unicode at times.$seeds | Out-File -Encoding ASCII narrowseeds.txt
Looks like the decrypted file contains some proprietary information The answer to this objective is Machine Learning Sleigh Route Finder.