People working for TMNS are IT consultants. They work at customer locations. They also follow the guidelines as set forward by the customer. If the customer prefers to work with DropBox for filesharing, we use DropBox for that project. If the customer wants to use Google Drive, we use Google Drive. If they want to use Git or BitBucket, we say “Cool, someone who knows what they are doing”. But I digress.
Unfortunately, following customer guidelines introduces a risk for us as well. At TMNS we have our data security in order, but having “third parties” accessing shared data means we do not have full control anymore. It so happens that for a certain client we found out that someone with access to that Google Drive got infected with the Cerber3 ransomware. Just search for more info on “cerber3” using your preferred search engine. But in short, what it does is the following:
- It takes over an MS Windows executable and highjacks it to do its evil thing
- Traverses the entire folder structure on all drives
- For each file, it finds it renames that file to some 10 character gobbledygook, with an extension “.cerber3”.
- Then, it encrypts that file and moves on to the next
- It also creates a file in each effected folder called “@___README___@.txt” (or .html or .url extension) that contains the very friendly offer to decrypt all your files. All you need to do is pay up.
But there’s more…
Unfortunately, Cerber3 also managed to traverse through all files in this user’s Google Drive folder. Since these folders were synced with Google Drive servers, it meant that everybody with access to these folders (so also our consultants) saw their local files stored in the impacted Google Drive folder garbled to something “.cerber3” extension as well. These users were not infected (infection occurs by executing the Trojan executable, something that is prevented by our policies on antivirus software). But it did mean we now had a bunch of files that we could not access anymore.
A cursory investigation told me that each infected file indeed was first renamed (step 3) and then encrypted (step 4). It also showed me that Google Drive created a new revision because of step 4. Simply put: an original file called “this_very_important_document.docx” was first renamed to “-67YHK58DF.cerber3”. This in itself did NOT create a new revision in Google Drive. But then it was encrypted and stored under the same name (“-67YHK58DF.cerber3”). This DID create a new revision.
I could manually remove the latest revision and then rename the original revision to the original name. The original name of a file is luckily stored by Google Drive when you rename a file. Doing this “remove revision 1, then rename revision 0” worked. But doing this for over 2000 files manually, kind of, well, stinks!
But doing this for over 2000 files manually, kind of, well, stinks!
So, let’s automate it
I know a thing or two about Java (although it has been 7 years that I did some real production-level coding), I also knew that Google provided an API to GDrive, so a solution should be possible. But first: break down the problem. The steps I thought I would need to take were:
- Find a way to programmatically access GDrive from Java, including authentication
- Find a way to list files in GDrive matching a certain criteria (guess what: the criteria would be “*.cerber3”)
- Find a way to retrieve revisions of each file
- Find a way to determine the latest revision of each file and delete it
- Find a way to get the OriginalFilename of the previous-to-last revision
- Find a way to rename the previous-to-last version and rename it to the original filename (so renaming “-67YHK58DF.cerber3” to “this_very_important_document.docx”
- Remove all “@___README___@.txt” (or .html or .url extension)
> Simple! Let’s do it!
What follows, is a sort of combined action/brain dump. I hope it helps someone.
Step 1: Java based access to Google Drive API
First, I needed a way to access GDrive from Java, including authentication. Of course I skipped this part and googled directly for “Java API access filelist”. So this step I did not actually do. Skipping to step 2…
Step 2: List GDrive files matching a criteria
Looked for a way to list all files matching a certain query. Found it at: https://developers.google.com/drive/v2/reference/files/list.
Looked for Java based example on how to execute. Found it at: https://developers.google.com/drive/v3/web/quickstart/java.
Followed the steps there (luckily I have a Macbook and I know my way around the shell).
In a nutshell, this is what I did:
Installed Gradle, following guidelines on https://docs.gradle.org/current/userguide/installation.html?_ga=1.256180747.1042631887.1474445767
Started an xterm in my home directory (yeah, just dumped this in my homedir)
Downloaded latest gradle.zip to this directory. Unzipped it. Then:
MacBook-Pro-van-Roeland-2:gradle-3.1 roelandlengers$ gradle -v
Build time: 2016-09-19 10:53:53 UTC
Ant: Apache Ant(TM) version 1.9.6 compiled on June 29 2015
JVM: 1.8.0_60 (Oracle Corporation 25.60-b23)
OS: Mac OS X 10.11.6 x86_64
vi-ed ~/.bash_profile to add it to my environment:
Started new xterm, checked if gradle worked (the gradle –v command). It did.
Then, I followed instructions at https://developers.google.com/drive/v3/web/quickstart/java, section 1.
I started the wizard.
Hit Create a project. Continue.
In the drop-down, top-left, select Create project again.
Entered “GDrive List” as name. Hit Create. Waited till this project shows up.
On the Credentials-Oauth consent screen, I entered my email, projectname “GDrive List”, hit Save.
Select the Credentials tab, click the Create credentials button and select OAuth client ID.
Select the application type Other, enter the name “GDrive List”, and click the Create button.
Click OK to dismiss the resulting dialog.
Click the file_download (Download JSON) button to the right of the client ID.
Move this file to your working directory and rename it to client_secret.json. I created a new directory on my machine:
/Users/roelandlengers/codingProjects/GDriveList, I stored the json file there.
In the directory /Users/roelandlengers/codingProjects/GDriveList I ran the following commands:
gradle init –type basic
(Build was successful)
mkdir -p src/main/java src/main/resources
cp client_secret.json src/main/resources/
Opened the default build.gradle file and replaced its contents with the following code:
apply plugin: ‘java’
apply plugin: ‘application’
mainClassName = ‘GDriveList’
sourceCompatibility = 1.7
targetCompatibility = 1.7
version = ‘1.0’
Noticed the “v3” in that last line? I didn’t at first, caused me a lot of grief.
Then, created a file src/main/java/GDriveList.java and inserted the example code from https://developers.google.com/drive/v3/web/quickstart/java (not going to repeat it here).
Then ran the example:
gradle –q run
The first time I ran the sample (my firewall kicked in, so I gave it access to all), it prompted me to authorize access:
The sample will attempt to open to open a new window or tab in your default browser. If this fails, copy the URL from the console and manually open it in your browser.
If you are not already logged into your Google account, you will be prompted to log in. If you are logged into multiple Google accounts, you will be asked to select one account to use for the authorization.
Click the Accept button.
The sample will proceed automatically, and you may close the window/tab.
It failed. In the console output it said something about:
“Access Not Configured. Drive API has not been used in project 790354171583 before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/drive/overview?project=790354171583 then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry.“
So, I pointed a browser to https://console.developers.google.com/apis/api/drive/overview?project=790354171583
Then clicked on Enable, next to Google Drive API. Took just a few seconds.
Then I ran the code again:
gradle –q run
Still didn’t work. “What the &^*%?” Then I noticed this line in the output:
“If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry.“
So I grabbed my ninth cup of coffee and retried after a few minutes:
gradle –q run
Now it worked!
Credentials were saved to /Users/roelandlengers/.credentials/drive-java-quickstart
OK, next: list files by query. We need to get all files that are now named something with “.cerber3”. How to do that?
First, changed the code to only list *.jpg files, in my drive (not in photos), checked if that worked:
FileList result = service.files().list()
.setFields(“nextPageToken, files(id, name)”)
OK, that worked. But I need to look for files with a specific name:
.setQ(“name contains ‘.jpg'”)
OK, worked as well.
Now I needed to find a way to manipulate these files.
But first: find out if the original filename of an altered file is still available in the history. For this I created a MS Word file (GDrive doesn’t store versions of native GDrive fileformats, that info is “in” the file itself). Then I renamed the file. Now I had to try to find all the info on that particular file.
I used the web interface from https://developers.google.com/drive/v2/reference/files/list to test if I could get to all info. – I could! On this page, there is a Try it!-option that allows you to execute an API call and see the entire result. Then I knew I could get:
1. all revisions of a file
2. the OriginalFilename
So, in order to keep the “flow” of this document I have to start a new section:
Step 3: Find a way to retrieve revisions of each file
Just did that!
Or so I thought… Since the Try It!-option on https://developers.google.com/drive/v2/reference/files/list, including the sample code, is based on API v2 and not on v3 (which comes by default if you use gradle to create your project). So I had to alter the examples to match the v3 syntax. See the final source at the end of this blog for the details.
Step 4: Find a way to determine the latest revision of each file and delete it
I took a shortcut here. I traverse over all files matching the .cerber3-criterium. Then I determine if there are exactly 2 revisions. If not: skip (but log to output).
When there are only two revisions, I know that the revision with the originalFilename that does not end in .cerber3 is the original and the other one is the encrypted file. OK, it’s a hack, but it works.
Step 5: Find a way to get the OriginalFilename of the previous-to-last revision
And this is where the lack of documentation really bit me. Turns out that when you get a RevisionList, you can’t get the details per revision at the same time. That’s why I had to get a list of revisions per file and then I had to retrieve the revision one-by-one again. Only to get the OriginalFilename. Anyway, this took a couple of hours to figure out.
Step 6: Find a way to rename the previous-to-last revision
Rename it to the original filename (so renaming -67YHK58DF.cerber3 to thisveryimportantdocument.docx.
First, I tried to rename the revision, but it had no effect. Then I decided to just rename the file itself. And that worked. – Hooray for trial-and-error. Did I already mention that this code is never going to go into “production”? I just had to find a way to get these files back so others could do their work.
Step 7: Remove all “@___README___@.txt” (or .html or .url extension)
Trivial. Not going to comment on that.
In the end I came up with the source-file below. I put in some documentation, perhaps it’s of use to someone. We had more than 2500 files affected. With a loop of 500 I had to run it multiple times. But in the end it turned out I only needed to manually adjust 3 files. And only because they were altered by someone after the infection. Not bad. Let’s do the math:
Manual work: 2500 times 1 minute (optimistic) = 41 hours.
Designing, coding, testing and executing the script: 10 hours
Add a few hours for writing this document: still a good result.
I would have been done in 3 hours, if it hadn’t been for the undocumented features of the Google Drive API. But as Benjamin Franklin said: “He that is good for making excuses is seldom good for anything else.”. Let’s leave it at that.
And a final conclusion: use a version control system for all your files, not only for source code. The fact that GDrive kept revisions is the only reason I was able to mitigate the effects of this piece of ransomware.
Thanks for reading. And again: I just hope this might be beneficial to someone. You can download the source file here.