Pages

Monday, March 5, 2018

NoSQL injection leading to administrator account takeover in Rocket.Chat (0.57.3, 0.58.3 and below)

Executive Summary

A regular user account can access sensitive data using a NoSQL injection vulnerability in the API provided by Rocket.Chat. Data includes usernames, email addresses, login tokens, password hashes and reset tokens of all users of the application including administrators. A malicious user can try to crack the password hashes or request a password reset to compromise accounts. Using that vulnerability, a regular user can get an administrator access on the application which can results in other sensitive data exposure such as conversations, LDAP configuration…

What is Rocket.Chat?

According to their website, "Rocket.Chat is the leading open source team chat software solution. Free, unlimited and completely customizable with on-premises and SaaS cloud hosting." Based on a tweet from last November, Rocket.Chat has been installed on over 150k servers and is used by more than 10 million people.

Detailed explanations

This vulnerability has been tested on Rocket.Chat 0.58.3, last stable release as of October 4th 2017. According to the source code on GitHub, the current development code is also vulnerable.

Two attacks scenario exploiting this NoSQL injection have been identified:

  • Password hashes extraction
  • Account takeover via password reset

Password hashes extraction

Request to the API to show the version:
2017-10-04-131626_900x318_scrot.png

Login to the API with a regular user:
2017-10-04-131959_1154x322_scrot.png

Check user privileges:
2017-10-04-132141_1037x389_scrot.png

The regular user “attacker” is of type “user” and doesn’t have access to administration features.

Our regular user is able to list administrators using the NoSQL injection in the “query” parameter:
2017-10-04-132255_1080x471_scrot.png

It is also possible to show hidden fields with the “fields” parameter:
2017-10-04-132454_1337x564_scrot.png


With this information, a regular user has access to usernames, email addresses and password hashes of administrators. He can use tools such as JohnTheRipper or Hashcat to try to find cleartext passwords. Passwords are hashed using Meteor’s Accounts-password library like this:
hash = bcrypt(sha256(password))

Account takeover via password reset

There is an easier/faster way to compromise administrator accounts, it is by using the “Reset password” feature. Before asking for a password reset, our victim data account looks like this:
2017-10-04-133054_1064x514_scrot.png

We can use his email address to request a password reset:
2017-10-04-133215_1357x589_scrot.png

After requesting a password reset, a malicious low privileged user (attacker) can request the API to get access to the password reset token:
2017-10-04-133249_1223x563_scrot.png

With that reset token in hands, a malicious user can reset the victim’s password (admin):
2017-10-04-133401_1359x604_scrot.png

2017-10-04-133451_1341x701_scrot.png



Timeline


October 4th 2017: Bug reported
October 5th 2017: Fix deployed
January 2nd 2018: Attribution of CVE ID CVE-2017-1000493
January 17th 2018: Official communication from Rocket.Chat about the vulnerability (Blog post Tweet Tweet)
March 5th 2018: Publication of this blog post


Thanks to the Rocket.Chat team that has been fast fixing the reported vulnerability


After a quick search on Shodan, I figure it out that there is another easy way to compromise a Rocket.Chat application ...


Tuesday, July 26, 2016

How I save my Strava activity

Last Sunday, when I reached my home after my bike ride I took my smartphone from my pocket to stop the activity and tried to put it back in it. But I missed the pocket and the phone fall (screen first) on the sidewalk. I was able to turn on/off the screen but the touch sensor was not responding so the phone was unusable.
Breaking a phone is always something annoying but when it contains your longest bike ride statistics not yet synchronized, it's worst.


I then spend some time to think about how I could access to the detail of my activity. I was only able to see the distance and duration on the lock screen, but that was not enough for me.


The easiest solution

While I was complaining talking about my broken screen and unsynchronized Strava activity on IRC, a friend (Edouard), suggests me to install a remote control software to access the phone screen via the computer. That idea was great and worked well.

For that I connected my phone via USB to my computer and used Vysor Chrome extension and adb. That's probably doable with any similar tools, but they can require to install an app on the device.

If the device is not Internet connected (like my phone that I used only to record bike rides), it may be necessary to enable WiFi with "svc wifi enable" command in adb shell.
If the phone is locked, it can be necessary to unlock it via adb: "input keyevent 66 && input text XXX && input keyevent 66" (XXX is the password, 66 is the key code for Enter).


The geek solution

When I thought about that issue, my first idea was to analyze files used by Strava on Android and my last option was dumping the memory to try to find some data related to my activity.

First thing first, I started looking for files on the file system of my phone where I can find data related to my Strava activity.

root@mako:/ # find . 2> /dev/null -name *strava*

A search like the previous one was able to list many files:

./storage/emulated/0/Android/data/com.strava
./storage/emulated/0/Android/data/com.strava/cache/cache_vts_labl_com.strava.m
./storage/emulated/0/Android/data/com.strava/cache/cache_vts_com.strava.m
./storage/emulated/0/Android/data/com.strava/cache/cache_vts_inaka_com.strava.m
./storage/emulated/0/Android/data/com.strava/cache/cache_vts_com.strava.0
./storage/emulated/0/Android/data/com.strava/cache/cache_vts_inaka_com.strava.0
./storage/emulated/0/.estrongs/.app_icon_back/ver/com.strava_419
./storage/emulated/0/.estrongs/.app_icon_back/ver/com.strava_512
./storage/emulated/0/.estrongs/.app_icon_back/ver/com.strava_516
./storage/emulated/0/.estrongs/.app_icon_back/com.strava.png
[...]
./data/data/com.strava/shared_prefs/com.strava.preference.excludeFromBackup.xml
./data/data/com.strava/shared_prefs/com.strava.preference.userPreferences.xml
./data/data/com.strava/shared_prefs/com.strava_preferences.xml
./data/data/com.strava/shared_prefs/apptimizecom.strava.xml
./data/data/com.strava/files/DATA_disk_creation_time_vts_labl_com.strava
./data/data/com.strava/files/DATA_disk_creation_time_vts_com.strava
./data/data/com.strava/files/DATA_disk_creation_time_vts_inaka_com.strava
./data/data/com.strava/files/event_store_v2_com.strava
./data/data/com.strava/files/DATA_ServerControlledParametersManager.data.com.strava
./data/data/com.strava/databases/strava
./data/data/com.strava/databases/strava-journal
./data/app/com.strava-1
./data/dalvik-cache/profiles/com.strava
./data/media/0/Android/data/com.strava

I started to analyze the cache directory. Each activity recorded by Strava created few files in that cache directory. Here is how the content looks like for 3 activities:

root@mako:/storage/self/primary/Android/data/com.strava/cache #  ls -l
-rw-rw---- u0_a111  sdcard_rw    76181 2016-07-21 20:02 cache_bd.0
-rw-rw---- u0_a111  sdcard_rw    17540 2016-07-24 08:33 cache_bd.1
-rw-rw---- u0_a111  sdcard_rw    49152 2016-07-24 08:33 cache_bd.m
-rw-rw---- u0_a111  sdcard_rw    22528 2016-07-19 19:49 cache_its.m
-rw-rw---- u0_a111  sdcard_rw    22528 2016-07-19 19:49 cache_its_ter.m
-rw-rw---- u0_a111  sdcard_rw    23257 2016-07-21 20:24 cache_r.0
-rw-rw---- u0_a111  sdcard_rw    32768 2016-07-21 20:24 cache_r.m
-rw-rw---- u0_a111  sdcard_rw  4061958 2016-07-24 08:33 cache_vts_com.strava.0
-rw-rw---- u0_a111  sdcard_rw    40960 2016-07-24 08:33 cache_vts_com.strava.m
-rw-rw---- u0_a111  sdcard_rw     2827 2016-07-21 20:02 cache_vts_inaka_com.strava.0
-rw-rw---- u0_a111  sdcard_rw    40960 2016-07-21 20:02 cache_vts_inaka_com.strava.m
-rw-rw---- u0_a111  sdcard_rw    27648 2016-07-19 19:49 cache_vts_labl_com.strava.m
drwxrwx--x u0_a111  sdcard_rw          2016-07-19 16:53 debug

These files contain probably some interesting data, but nothing understandable without in-depth analysis of the app.

The find command also return a SQLite database (./data/data/com.strava/databases/strava) which is easier to understand and contain a lot of information.

Database description

This is a listing of the tables present in this SQLite database with a short description of their content when I was able to understand it. I didn't try to analyze everything as I mainly focus of the unsynchronized activity.

- activities: Information of your user's friends activities
- activities_unsynced: Unsynchronized activities
- android_metadata: Only store the language
- annual_progress_goals: Annual total distance for running biking and swimming of user's friends
- athlete_clubs
- athlete_contact
- athlete_stats: Number of activities, distance, duration and elevation gain for running, biking and swimming for recent activities, for the year and since the user uses the app for him and all his friends. NB: the numbers can be not accurate (doesn't seem to be updated frequently)
- athletes: Details about the athlete profiles visited included the profile of the user (with more details than the other athletes)
- challenge_leaderboards: Summary of the leaderboards for each challenge subscribed by the user
- challenge_participants: Information about which user's friend is participating to which challenge
- challenges: Information about the challenges available
- clubs
- comments
- dorado_impression: A unique URL to a 1x1 gif used to track something (probably Premium ads printing)
- facebook_search: Information about the user's friends found via Facebook
- feed_entries: Information about activities shown in the app (via clubs, challenges for exemple)
- followers: Information about athletes following the user
- followings: Information about the athletes followed by athletes in the 'athletes' table
- froutes
- gear: List of gear (shoes, bikes) and distance traveled with
- kudos
- live_activities
- live_activities_points
- live_athletes
- live_events
- live_location_activities_gson
- live_matches
- live_tracking_contacts
- notification_settings
- notifications: Information about app notifications
- progress_goals: Progress goals about athletes from 'athletes' table
- promo_overlay
- related_activites
- routes: Route information when they have been printed on screen (for any kind of user)
- rts_logs: Geolocalisation data for unsynchronized activities
- segments: Information about activities segments
- sensor_datum: Information ('relative_altitude', 'pause_type') for unsynchronized activities
- streams                                                                                      
- training_videos
- unsynced_photos
- waypoints: Details (latitude, longitude, altitude, speed, elapsed time, speed) for unsynchronized activities
- zones


Let's get my data back


The previous tables listing shows some interesting tables, like "activities_unsynced" which contains some meta data about all unsynchronized activities:

sqlite> select * from activities_unsynced;
id|updated_at|json
3|1469393124028|{"activity_id":0,"auto_pause_enabled":false,"commute":false,"distance":84499.02644972557,"elapsed_time":0,"end_timestamp":0,"guid":"REDACTED","is_private":false,"live_activity_id":0,"row_id":3,"m_end_battery_level":-1.0,"m_initial_elevation":94.42694,"m_screen_on":false,"m_screen_on_start":53574812,"m_screen_on_time":723567,"m_screen_timer_invalid":false,"m_start_battery_level":0.99,"sync_state":"UNFINISHED","manual":false,"name":"2016-07-24","photos":[],"route_id":-1,"sensor_averages":{"1":{"a":10000,"b":0,"c":0.0,"e":true},"0":{"a":10000,"b":0,"c":0.0,"e":true},"10":{"a":10000,"b":0,"c":0.0,"e":true},"18":{"a":10000,"b":0,"c":0.0,"e":true}},"should_facebook_share":false,"start_timestamp":1469374573560,"type":"Ride","video_view_id":-1,"workout_type":-1}

Interesting data include:
- distance
- elapsed time
- start timestamp
- end timestamp
- guid (unique ID of the activity)
- name
- type
- synchronization state

NB: The dot in the timestamp is missing (1469374573560 -> 1469374573.560) and the distance is recorded in meters (even if the application is configured to use miles).


An other interesting table is "rts_logs" as it contains few GPS coordinates of the user during its activity:

sqlite> select * from "rts_logs";
id|updated_at|activity_guid|json
76|-1|REDACTED|{"m_activity_guid":"REDACTED","m_log_json":"{\"latlng\":[37.74XXXXXX,-122.43XXXXXX],\"timestamp\":1469374596336,\"failure_code\":1001,\"failure_status_code\":-1}","m_type":"request"}
[...]
sqlite> select count(id) from rts_logs;
count(id)
67

This table can be used to track the user using latitude, longitude and timestamp but 67 records for a 84km/52miles ride, it's not that much.

Hopefully another table contains more detailed information about the activity, and that table is "waypoints":

sqlite> select * from waypoints;
ride_id|pos|timestamp|latiude|longitude|altitude|h_accuracy|v_accuracy|command|speed|bearing|device_time|filtered|elapsed_time|distance
REDACTED|0|1469374597000|37.74XXXXXX|-122.43XXXXXX|18.60XXXXXXXXXXX|9.0|||7.0|86.3000030517578|1469374596293|0|10241|0.0
[...]
REDACTED|17356|1469393123000|37.74XXXXXX|-122.43XXXXXX|34.X|3.0|||0.0|0.0|1469393123969|1|15800446|84499.0264497256
sqlite> select count() from waypoints;
count()
17357

We now have 17357 GPS coordinates to recreate the activity map (using OpenStreetMap or Google Maps) and do some statistics!

I hope that this understanding of the Strava SQLite database can be useful for any person in a similar situation as me (broken phone with unsynchronized activity) or for forensics analysis (lot of GPS data in it)!

Tuesday, April 7, 2015

Security update issue on Lenovo tablet

Few days ago, I spend some of my free time checking what my chinese android tablet (Lenovo Yoga 2) was sending on the Internet. I quickly identify some interesting HTTP requests.

As most of the manufacturers, Lenovo ships their tablets with additional software developped by Lenovo or others.
The GameStore app is one of them. That application is checking if updates are available on HTTP and if it find one, downloads it on HTTP too:


The APK file is also available on HTTPS but not used!

The GameStore app can be used to buy new (unknown) games, and seems to be vulnerable to price tampering (not fully tested because stealing is as bad as games on this store). After an update, they remove the payment method which was previously easily vulnerable.

Some Lenovo apps seem to have a similar behavior. They check for updates over HTTP, but I have not been able to trigger any update download :/
Update requests looks like that:


Hey Lenovo, why do you need to know my (private) IP address ?

Other update requests are sent to http://susapi.lenovomm.com/adpserver/GetVIByPNUser with different parameters.

The most interesting HTTP requests I saw was those relating to firmware update! 
The query update looks like that (as my tablet was already up-to-date, I changed my firmware version in the request):

And the answer is:

So Lenovo applications and firmware update on HTTP. Come on guys, we are in 2015!

NB: Tests have been done on YT2-830F_USR_S000143_1501051826_WW21_ROW firmware. Few days after I notice Lenovo Security team, they publish a new firmware (YT2-830F_USR_S000184_1503241129_WW21_ROW) which do not fix that issue... 

Friday, May 24, 2013

OSX Kitmos : other binary, other C&C


On May the 20th, Norman has published a report about an Indian cyberattack infrastructure that they call "Hangover" due to information found to the path to a PDB file.
Their blog post also refers to Oslo Freedom Forum attack that I wrote about in my previous article :
"Based on the sample and Command&Control domain mentioned in the F-Secure post, we can say quite conclusively that the Oslo Freedom Forum attack was performed through the same attack infrastructure. We also found another MachO executable apparently written by the same person (same Apple Developer ID), and using another domain in the Hangover infrastructure – torqspot.org."

As this domain was present in another Mach-O binary that I have, I have chose to take a quick look at it.



File informations

SHA1 hash : b6a47d52de64af50a5a1415213e60dc1b076b4e7
File type : Mach-O executable i386
VirusTotal report : https://www.virustotal.com/en/file/a74196018b2854765333a8f798b0ae3f3b71c89ec9632188f07c71d055125cb2/analysis/

C&C information

This sample uses "torqspot.org" as C&C domain name. Whois reveals still fake information :

Domain ID:D168171472-LROR
Domain Name:TORQSPOT.ORG
Created On:16-Mar-2013 05:28:07 UTC
Last Updated On:16-May-2013 03:45:16 UTC
Expiration Date:16-Mar-2014 05:28:07 UTC
Sponsoring Registrar:PDR Ltd. d/b/a PublicDomainRegistry.com (R27-LROR)
Status:CLIENT TRANSFER PROHIBITED
Registrant ID:DI_25590875
Registrant Name:Melissa Leo
Registrant Street1:E-5 cecill street
Registrant Street2:Manchester
Registrant City:Manchester
Registrant State/Province:Manchester(Cityof)
Registrant Postal Code:M14LF
Registrant Country:GB
Registrant Phone:+044.7251868
Registrant Email:leo.melissa@mail.ru

C&C is not responding anymore.

IOC

All logged messages can be used to identify a compromised macintosh. For example, the following messages can be used as IOC :

- "http://torqspot.org/App/MacADV/up.php?cname=%@&file=%@"
- "CONTACTS mreslt %@"
- "CONTACTS urlResponse  %d"
- "responseData: %@"
- "http://torqspot.org/App/MacADV/$hostname/$serverResponse"
- "/Applications"
- "End"
- "app path =%@"
- " exec path =%@"
- "file: %@"
- "connected to upload server %@"
- "Fail connected to upload server %@, begin in %d sec"
- "Try zip and upload for failed file, before."
- "ComputerName_UserName : %@"
- "Failed retry %@"
- "Retry %@"
- "New seesion"
- "search path from state.dat"
- "search path from root"
- "available paths: %@"
- "No found folder"
- "No found file"
- "Start searching"
- "%ld files found"

DNS resolution to "torqspot.org" and all kind of HTTP requests to this domain can also be used to identify a compromised computer on a network.

Features

Lots of features and functions (coml, cop, runSystemCommand, ...) are similar to previous binary analyzed. Below, only new and interesting will be detailed.

macurl

- send synchronous HTTP request to "http://torqspot.org/App/MacADV/up.php?cname=%@&file=%@" w/ hostname as 1st arg and "no" as 2nd arg
- get data at URL "http://torqspot.org/App/MacADV/$hostname/$serverResponse" w/ dataWithContentsOfURL function, where $serverResponse is the response sent by the server to the previous request
- write downloaded data to file "/Applications/$ServerResponse"
- execute following command :
"/usr/bin/ditto -x -k /Applications/$ServerResponse /Applications/" to extract PKZip archive "/Applications/$ServerResponse" to "/Applications/"
- replace/add ".app" extension to "/Applications/$ServerResponse"
- if path exists, run the executable (NSTask, setLaunchPath, launch)
- create string "http://torqspot.org/App/MacDV/up.php?cname=%@&file=%@&res=%@" w/ arguments : $hostname, $serverResponse and "sucess" (w/ one 'c' :)
- send a request using that string (w/ sendSynchronousRequest:returningResponse:error: method)
- log "file: %@" w/ data answered by the server as argument

initFileBackup

- get bundlePath and add "FileBackup.ini" to it
- use "stringWithContentsOfFile:encoding:error:" function to get content data of config file ("<bundlePath>/FileBackup.ini")
- if file content iss less than 10 characters, go to the end of the function
- extract data between <URL> and </URL> to pass as parameter to setUrl function
- extract data between <EXTENSION> and <EXTENSION> and use it to create an array of strings based on ';' separator
- call setExtArray to initialize an array with extensions stored in "FileBackup.ini" file

before_start_

- get bundlePath and add "state.dat" to it
- if that file exists, read its content and create an array of strings by spliting on "#####" separator. Then use strings in that array as paths
- if the file doesn't exist, path will be set to "/"
- call connectServer/upload of ZipUpload class and run a command similar to this : "/usr/bin/curl -F upload=@ -F pc="

find_

Looking for files based on extension

batch_

Do some stuff and call macurl

deleteState_

Delete file "<bundlePath>/state.dat" if it exists

saveState_

Save a string array to "<bundlePath>/state.dat" by separating strings by "#####". This function is called by find function and the malware terminates.


Refs

https://github.com/gdbinit/fixobjc/blob/master/fixobjc.idc
http://www.f-secure.com/weblog/archives/00002554.html
http://threatpost.com/new-mac-malware-discovered-on-attendee-computer-at-anti-surveillance-workshop/
http://blogs.norman.com/2013/security-research/the-hangover-report
https://www.botnets.fr/index.php/HangOver

Monday, May 20, 2013

OSX Kitmos analysis


On 16th of May, Sean Sullivan has published an article on F-Secure blog about a new Mac OSX malware discovered on the Mac of an African activist by Jacob Appelbaum during an Oslo Freedom Forum workshop.

File information

SHA1 hash : 4395a2da164e09721700815ea3f816cddb9d676e

According to file Unix command, this binary is a Mach-o executable containing x86 and x64 code. VirusTotal repport of this binary can be found here. With a really quick look at the sample, we can see that it is not packed, obfuscated or encrypted.

C&C information

This sample contains two C&C url which in fact are at the moment pointing to the same server at IP 50.116.28.24 (This differs from F-Secure blog post, where IP addresses of both domains where different). This IP address points to Linode hosting company.

A whois on "securitytable.org" reveals these (fake) information :

Domain ID:D168053198-LROR
Domain Name:SECURITYTABLE.ORG
Created On:04-Mar-2013 06:58:36 UTC
Last Updated On:16-May-2013 16:02:07 UTC
Expiration Date:04-Mar-2014 06:58:36 UTC
Sponsoring Registrar:PDR Ltd. d/b/a PublicDomainRegistry.com (R27-LROR)
Status:CLIENT TRANSFER PROHIBITED
Registrant ID:DI_26714386
Registrant Name:Christopher
Registrant Organization:N/A
Registrant Street1:DE-10387
Registrant Street2:Nairobi
Registrant Street3:
Registrant City:Nairobi
Registrant State/Province:Central
Registrant Postal Code:50563
Registrant Country:KE
Registrant Phone:+254.204973957
Registrant Phone Ext.:
Registrant FAX:
Registrant FAX Ext.:
Registrant Email:n.christopher@mail.ru

Whois information of "docsforum.info" domain are similar.

IOC

All logged messages can be used to identify a compromised macintosh. For example, the following messages can be used as IOC :
- " before ==%@"
- "path == %@"
- "path2===%@"
- "Hellooo"
- "Copy successful"
- "Upload response %@"
- "the path =%@"
- "path1 =%@"
- " Error - Statistics file upload failed: "%@""
- " the array value =%@"
- path to $HOME/MacApp
- date in this format : "yy-MM-dd-HH:mm:ss"
- ComputerName_UserName : $hostname-$username"
- "Start file zip : %@"
- "Start file zip : %@"
- "Start file upload : %@"
- "finished zipping file"
- "finished uploading file"
- "file path==%@"

Network traffic can be useful too to identify a compromised Mac on your network. A compromised macintosh, will generate DNS requests to "securitytable.org" and "docsforum.info" domains which, at the time I'm writing these blog post resolves to 50.116.28.24. HTTP requests to "http://securitytable.org/lang.php" and "http://docsforum.info/lang.php" will also reveal the compromission.

Features

sub_1E72

Function "sub_1E72" is responsible of the persistence of this malware. In fact, this function add the malware to the list of items to start at session login.





- get malware bundle path thanks to NSBundle.bundlePath
- call LSSharedFileListCreate with kLSSharedFileListSessionLoginItems as ListType in order to access to the list of applications starting when user logged on
- call LSSharedFileListInsertItemURL to add the path to the malware bundle to the list

PS : The malware is added to startup items of the current user only, if the malware author would like to start is program for all users on the system, he must use kLSSharedFileListGlobalLoginItems list type as LSSharedFileListCreate argument.

More information about this technique can be found here : http://cocoatutorial.grapewave.com/2010/02/creating-andor-removing-a-login-item/

cop

The first time the binary is executed, it copy itself to $HOME/<bundle_name>.app. Then, the sample calls "coml" function with the path to the new place (or its actual place, if it's not his first execution) as parameter. 

The call to "coml" will result of the execution of the following command : "/bin/sh -c open -a $HOME/<bundle_name>.app" (see coml & runSystemCommand below)

coml

Prepare "open -a $arg" NSString for "runSystemCommand" function.

runSystemCommand

Execute "/bin/sh -c $arg".


- create a NSArray via arrayWithObjects method used as command line options for "sh" and containing : "-c" and runSystemCommand argument
- create NSTask and call launchedTaskWithLaunchPath:arguments: method with "/bin/sh" and previous NSArray as argument

uploadRequestFinished

This function log some information like the response string received from the server or the path of the uploaded file. Then, the uploaded file is removed from the file system with a call to removeItemAtPath.

uploadRequestFailed

Log " Error - Statistics file upload failed: "%@"" where %@ is replaced by localizedDescription returned string.

sendRequestToServer

This function is sending hostname of the compromised macintosh to C&C server thanks to an HTTP request to http://docsforum.info/lang.php URL.

- create "http://docsforum.info/lang.php" URL for ASIFormDataRequest
- get hostname via NSProcessInfo.processInfo.hostName
- call stringByReplacingOccurrencesOfString on hostname value to replace '.' by 'p'
- log that new "hostname" string
- add hostname value to the HTTP request as a POST data named "cname" (addPostValue function)
- do the request w/ startAsynchronous

NB : I don't know if this function is called somewhere as I haven't found any xref to this function

getscreenshot

This function is used to take screenshots w/ screencapture Mac OSX binary and save them in $HOME/MacApp directory. Name of the screenshot follow this format : "yy-MM-dd-HH:mm:ss.png". This function is first called inside applicationDidFinishLaunching function. Screenshots are saved every 20 seconds.



- create $HOME/MacApp path (with NSHomeDirectory, stringWithFormat functions)
- this path will be log in Apple System Log via NSLog
- use the shared file manager to play with FS (via NSFileManager.defautlManager)
- check if the path exists (via fileExistsAtPath)
  - If it doesn't exist, create directory with createDirectoryAtPath
- get date string with this format "yy-MM-dd-HH:mm:ss" (via NSDate, NSDateFormatter, setDateFormat, stringFromDate)    // the date format is different from F-secure screenshot. here, use of ':' instead of '/' on F-Secure screenshot
- date is logged (NSLog)
- create string $HOME/MacApp/yy-MM-dd-HH:mm:ss.png
- create a NSTask object to run a program as a subprocess
  - defines the executable path to "/usr/sbin/screencapture" via setLaunchPath
  - arguments are passed to screenshot via setArguments
    /usr/sbin/screencapture -x -T 20 $HOME/MacApp/yy-MM-dd-HH:mm:ss.png : take a screenshot without any sound after a 20 seconds delay and save it to the aforementioned path
  - task is launched via launch method
- to finish, uploadImage function is called

uploadImage

This function is used to upload screenshots to "http://securitytable.org/lang.php"

- create NSUrl object with string "http://securitytable.org/lang.php" and use it to create an ASIFormDataRequest
- create MacApp directory path string and log it (like in getscreenshot function)
- get the hostname of the computer, thanks to "hostname" method of NSProcessInfo object (process information agent of the process)
- check that the path to MacApp folder exists. Go to end of the function if not
- count the number of files/screenshots w/ contentsOfDirectoryAtPath and use count function on the returned string array
- log the number of files/screenshots
- get first file of the list
- log " the array value =%@" where %@ is replaced by the name of the first file
- If ".DS_Store" exists, remove it from the file array
- Loop to take and upload screenshot
  - create NSData with content of each file/screenshot (via initWithContentsOfFile)
  - call ASIFormDataRequest.addPostValue function and set $hostname data to key "cname"
  - second call to addPostValue with key "name" and value : path to the screenshot
  - then call to setData ("setData:withFileName:andContentType:forKey:")
    key : userfile
    content type : image/png
    filename : path to the screenshot
    data : file content (screenshot)
  - upload the file/screenshot
  - call getscreenshot function

Summary

This Mac OSX malware is really simple. It has only few features :
- start at user login
- take screenshot
- upload screenshot

In addition, it is absolutely not stealth as screenshots are saved in $HOME/MacApp directory of the infected user. No advanced malware techniques/features (packing, encryption, obfuscation) have been seen in this sample.

Refs

https://github.com/gdbinit/fixobjc/blob/master/fixobjc.idc
http://www.f-secure.com/weblog/archives/00002554.html
http://threatpost.com/new-mac-malware-discovered-on-attendee-computer-at-anti-surveillance-workshop/

Monday, April 1, 2013

Analysis of an APT1 binary

In middle of February, Mandiant has released a huge report about cyber threat from Chinese government. Some of the technical details has been disclosed in Appendix C ("The Malware Arsenal") of their report.

Because of this APT buzz, I decided to take a look on one of the binary mentioned in APT1 report in order to know the level of this cyber threat.

After running a script on around 200 samples from APT1, I decided to analyse the binary which look the most strange. Report on VT can be found here.


According to PeID, this binary is not packed but it has 4 ".upx" sections and the OEP is pointing to the
last ".upx" section which is not a normal behavior. The few functions in the import table, the few strings in the binary and a high entropy in all sections confirm that point, the binary is packed! I thought interesting to take a look on this binary in order to understand and maybe discover the packer used as PeID failed to identify it.

Unpacking

Beginning of the packed code (in last .upx section : 0x8000) contains a lot of junk code. After these useless instructions, a loop is used to modify the code which follow the loop. The code after this loop is used to get addresses of LoadLibrary and GetProcAddress thanks to the ImportTableAddress field of the loaded PE file.

LoadLibrary is then used to load "kernel32.dll" and GetProcAddress to get addresses of the following functions :
GetModuleHandleA, VirtualProtect, GetModuleFileNameA, CreateFileA, GlobalAlloc, GlobalFree, ReadFile, GetFileSize, CloseHandle, CreateSemaphoreA, ReleaseSemaphore, Sleep, WaitForSingleObject, CreateThread

After the resolution of these functions, SizeOfImage in PEB->PEB_LDR_DATA->InLoadOrderModuleList is set to 1000 (previous value was 9000). Then, VirtualProtect is called in order to change access to ImageBase of the binary to PAGE_READWRITE.

Then some strings will be decoded from memory before being used for example to load library "kernel32.dll" again and then resolve addresses of VirtualProtect, VirtualAlloc,VirtualFree. As soon as the string has been used by LoadLibraryA or GetProcAddress, the string is replaced by several 0 in memory. With this kind of protection, the process contains few information in memory, so a dump of the actual process memory will be not really interesting for an analyst.

Then, code jumps to the third section (.upx 0x7000) which do similar stuff. Idem for the second .upx section.

The first .upx at RVA 0x5000 (but the last called, as order is reversed) will do similar stuff than previous sections but as this is the last packed section, these actions should be more interested for the unpacked binary.

In fact, this last .upx section will load all DLL used by the final (unpacked) binary and will resolve addresses of all functions :

kernel32.dll : CreateProcess, GetLongPathNameA, GetTempPathA, Sleep, CloseHandle, GetModuleHandleA, GetCommandLineA, GetModuleFileNameA, GetProcAddress, LoadLibraryA, ExitProcess

LZ32.dll : LZCopy, LZOpenFileA, LZClose

MSVCRT.dll : strstr, strncmp, atoi ...

ADVAPI32.dll : RegSetValueExA, RegCreateKeyExA, RegCloseKey

Then code jump in .text section, and OEP is then correct.

This packer uses several tricks to annoy the disassembler/analyst like some jumps in middle of an instruction, "push eax; retn", always true comparison ... but nothing to detect the presence of a debugger or a VM.


Malware features

After unpacking, this binary is in fact a simple downloader. The binary try to confuse IDA with lots of "JZ/JNZ" jumps. In fact, nearly all jumps are using this trick.

IDA confused

Disassembly fixed
The first function called by the unpacked code is used to resolve addresses of network functions. "wininet.dll" library is loaded at runtime with LoadLibrary and then InternetOpenUrlA, InternetOpenA, InternetCloseHandle and InternetReadFile functions addresses are resolved thanks to GetProcAddress. Address of UrlDownloadToFileA from "urlmon.dll" is also resolved with the same method.

The second function used is to assure reboot persistence to the malware. In order to run after a reboot, this malware add an entry to the registry :

[HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run]
"McUpdate"="path_to_this_binary"

Key and subkey values are not stored in clear text in the file but are encoded with a xor algorithm. The following python script decodes all encoded strings used in this binary :



Then malware contacts its C&C to get an order. URL is encoded with the aforementioned algorithm. After decoding, URL is : http://216.15.210.68/197.1.16.3_7.html. After decoding, User-Agent used to connect to the C&C is "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)". If it can't contact the C&C, it will wait 10 minutes before retry. After 3 fails to get a command from the C&C, the binary ends its execution.

Commands on the C&C server are between "<!-- DOCHTML" and "-->" HTML tags. These two tags like the following commands are also encoded in the binary. Command can be one of the following :
- Ausov : Exit the program
- Author X : Wait X * 10 minutes
- http://url : url used to download and execute an other binary.

When the last command is obtained from C&C, the file pointed by the url is downloaded to the temporary folder thanks to UrlDownloadToFile function. As the downloaded file can be compressed by Lempel-Ziv algorithm, the malware opens the downloaded file with LZOpenFile. An other file is created with nearly the same path than the first file opened : ".exe" extension is added at the end. Then, content from the first file (the downloaded one) is copied (and decompressed if necessary) to the second thanks to LZCopy function.

After that, the copied (and uncompressed) file is executed with CreateProcessA.


After analysis, the aim of this malware is simple : download and execute (more advanced?) binaries on the victim computer. This malware seems to be one of the WEBC2-AUSOV family defined by Mandiant as "Ausov" is a command of this sample.

I have not being able to identify the packer used. So if you recognized it, feedback will be welcome ;-)


Sunday, June 10, 2012

Make Dionaea stealthier for fun and no profit

I'm in my "honeypot playing period" and I've tried to scan my Dionaea with Nmap which detect of course lots of port listening but more annoying, last versions of Nmap are able to see that some services are provided by Dionaea ...



So if you want your Honeypot to be stealthier you can apply some tricks. Before to modify Dionaea services behavior, you have to know how Nmap services fingerprint feature works (I will only speak about Nmap, because that's the most used ports scanner, it's up to you to try with others).

In order to be able to discover the name and version of a service, Nmap use Perl Compatible Regular Expressions. All these regexp are stored in /usr/share/nmap/nmap-service-probes (path can change according to OS). If you want to understand nmap-service-probes file's syntax, I recommend you to read this. Below, some probes extracted from this file :



So if we want to hide our Dionaea honeypot from Nmap users, we have to modify Dionaea behavior to unmatch Nmap probes. First, list all Dionaea probes of this file :

[steeve@omega ~]$ cat /usr/share/nmap/nmap-service-probes | grep Dionaea



We can see that Nmap is able to detect "only" 4 services offered by Dionaea : FTP, HTTP, MSSQL and SMB. I will show you how we can deceive Nmap by modifying few files in Dionaea. I won't show you how to tweak MSSQL service because I haven't make deeper and this service looks a bit more complicated ... (If you have a solution, you can send me a mail or share in comments :-) 

First, if we look at the FTP probe, we can see that Nmap only checks the connection banner. So we just have to change it, and Nmap will be lost in its attempt to retrieve service name and version. For sure we can put any banner, but the best thing to do (in my opinion) is to try to act like a real FTP server. Shodan is a great tool to help us to know how to simulate FTP servers, check this link. I have choose to use MS FTP banner : "Microsoft FTP Service".

So we have to edit the Ftp python file located in : /opt/dionaea/lib/dionaea/python/dionaea/ftp.py. Now you just have to replace "Welcome to the ftp service" by the banner of your choice :



If we check HTTP Nmap probe, we can see that's a static one, no regexp used. This probe is based on HTTP headers and HTML source code. There is at least two simple solutions. We can see that HTTP service lists the directory content, so first we can decide to simply put a file in /opt/dionaea/var/dionaea/wwwroot directory, and HTML source code will be different and won't check probe anymore. The second solution is to modify the HTML code sent by Dionaea in /opt/dionaea/lib/dionaea/python/dionaea/http.py. For example, in list_directory(), we can change DTD, title page ...



SMB probe provided by Nmap is based on the value of two fields of the SMB Negotiate Protocol Response : "OemDomainName" and "ServerName". Nmap expects to receive respectively "WORKGROUP" and "HOMEUSER-XXXXXX" where X represent random data. It seems quite easy to mislead Nmap on SMB service too. We just have to modify those values in  SMB_Negociate_Protocol_Response class of file /opt/dionaea/lib/dionaea/python/dionaea/smb/include/smbfields.py. Let's try with "HINMAP" and "TRYHARDER".



You can see results of our tricks just below. Sure, that's not perfect but it's better than nothing ;-)



In this blog post, I've shown you how to use Nmap probes to "protect" your honeypot, but you can do the opposite adding new probes to get a more powerfull Nmap. In addition, it will be interesting to modify MSSQL behavior and SSL certificates to obtain a no verbose honeypot (look at the first scan for SSL certificates details).

FYI : Markus, Dionaea's creator, won't fix Dionaea regarding to Nmap (or other scanners) possible detection. It's a cat-and-mouse game that he can't win because some protocols are tricky to implement and modify whereas Nmap probes are very easy to add. You can read this mail on Nepenthes mailing list.