local time and we just checked into Circus Circus. It is so classy!
There is clown art everywhere, and parts of the room are coin
operated. The entire downstairs floor is a freaky
clowncasinomallfoodcart. We're going to go eat!
Update 1: See the last section below (The Code) for my Ruby code.
Update 2: Yes, I've written a server that acts just like the real one and will post the code soon.
Update 3: Decompiled RankMe.class is here: RankMe.java
Update 4: The official t400 server source has been released.
The pt400 challenge was a game in which you had to correctly rank a set of 71 famous people (including hackers in both senses of the word [e.g., Kevin Mitnick, Richard Stallman], famous coders [e.g., Knuth], cryptographers [e.g., Bruce Schneier], etc.). I'll call them all hackers for simplicity.
The game client was a simple Java Swing app called RankMe. You run it from the command line and it connects to the game server. Then, it presents two hackers and you have to pick the better one. If you pick the wrong hacker, the message "Better luck next time!" pops up and the game exits. The ranking of hackers was chosen by DDTek, I think somewhat arbitrarily, and is consistent from game to game.
The first thing I tried was to analyze the game traffic with Wireshark. This was pretty fail. There was no JPEG/PNG/GIF/BMP data in the stream, and no understanding of the protocol immediately came to me. I could have spent more time analyzing it, but instead I decided to decompile the client with JD-GUI (http://java.decompiler.free.fr/). This worked perfectly and gave me RankMe.java, which was easy to read and understand. I discovered how the game protocol worked by reading this code.
The protocol is simple. Basically, the server sends two images to the client at a time, and after receiving each pair of images and displaying them to the user, the client sends back the user's guess. If the user is right, the client can download two new images. If the user is wrong, the server sends an error code and disconnects. If the user has guessed enough matches correctly, the server asks for the combination of guesses that won (a string of LRLR..). If the user correctly inputs that combination, the server sends a secret message which is the key for pt400.
My strategy for winning was simple. First, I needed to get the outcome of a bunch of matches so that I'd be able to predict future matches. To do that, I implemented the protocol in Ruby in a class called Game which could connect to the game server and play games forever, collecting match results in a file called gamelog. (The Game class played stupidly, always picking the left picture.) The server was able to run about one round a second, and I had about 3,000 match results by the time I was ready for the next step. The match results (gamelog) looked like this:
9032d9fc7f0eec084636a35a0a623e84b2ee91a0 beat 895de4c13c326b60730786288465f91b5e5ba982
61ed2a78cf49d8c7eaa598fcbd9805825fae291c beat b94386c69bb0f04efe15863c480a2ba73bb7895d
2aea1a34c5cea68d5dd0678f04749e956935a11f beat 7b2fc387378a45a70c9db5cd384d3729ece8b3e7
50232549de83970517f50a6956e905004534919b beat f2f84e9514243c187cda82828569994a49ca481b
6e29bc1551abebad76297c5e3028a14188298d77 beat 815fc7e20b430cb34e98b632e081c8cfc0cbf9fa
(I uniquely identified each hacker by the SHA1 hash of his picture. For example, ac1dburn was ab74176394caeb1158df9c4d9fac004fff27377a, and Tsutomu Shimomura was 39ddc6e687b0cd8255ea44a0a9a7b2cb88601454.)
The next step was to write an extension of the Game class called SmartGame which knew the results of all the matches and, given two hackers, could predict which one would win the match. Here's the prediction logic:
My original plan for SmartGame was to have it learn from the game as it played; however, it turned out that having a gamelog of 3,000 matches made this unnecessary. SmartGame was able to pick the correct hacker 100% of the time. It could connect to the server and win in less than 30 seconds (or maybe a little longer, but it was fast).
Update: I updated my code to learn from the game as it's played, just because it was an easy change.
The secret key was None hold a candle to Bessie the sh33p!
My pt400 solver is written in Ruby. It's messy. Sorry for the delay in putting it up. I meant to do it right after quals.
Check it out of Github (or just download protocol.rb and smart.rb). To run it, do this:
I was talking to someone today who knew about rsync's bandwidth control, but not about scp's. Yep, they both have it.
rsync, you specify the bandwidth limit it KB/s.
rsync --bwlimit 200 foo server:bar
My common usage is:
rsync -avz -e ssh --bwlimit 200 --progress foo server:bar
scp, you specify the limit in Kb/s (kilobits vs. kiloBytes).
scp -l 1600 foo server:bar
You can make SSH much faster with the ControlMaster configuration directive. When enabled, only the first SSH connection to a server will incur slow connection overhead. Each additional connection will simply reuse the first connection via a tunnel.
This is especially useful when using command-line completion over SSH (using zsh, for example). Using tab completion on a predicate like
scp gibson:www/ht will result in a multi-second delay each time, but with SSH ControlMaster connections, the delay is sub-second.
Just add this to your ~/.ssh/config:
Host * ControlPath /tmp/%r@%h:%p ControlMaster auto
That's it. Now your first SSH connection will automatically act as a ControlMaster. Each subsequent connection will be almost instantaneous. In my extremely scientific sample size of one host-server pair, this made subsequent connections about 16 times as quick (0.1 seconds to connect instead of 1.6).
About a year ago I wrote a script that crawled my Twitter network and made me follow lots of new people. Then it waited 24 hours to see if they followed me. If they did, I kept them in my list; otherwise, I chopped them. After a couple of days I had almost 600 followers. I just did this for fun, and I knocked it off after a couple of days for fear of being banned.
I wish I'd recorded stats from the beginning of the experiment, but I only started recording them in August (2009). This is what it looks like so far.
This is a method I use when working with slow ruby scripts that operate on huge datasets.
It caches the return value of a block of extremely slow code in a file so that subsequent runs are fast.
It's indispensable to me when doing edit-debug-edit-debug-edit cycles on giant datasets.