I only made it to level 5, but I see that the later levels would have stopped me in my tracks anyway! I'm curious to see other solutions, especially for level 8. Was brute-forcing with a script really the only way?
It's a bit messy, as my original solution didn't work on the live server and I had to rethink it. My second plan was to reduce it to a handful of potential chunks I could brute force. Once I got it running, though, I realized it was eliminating about half of the chunks on each pass, which left me with the correct one in 7-13 iterations.
I took a similar approach. When I found a guess that returned a result that didn't make sense (due to the jitter/business of the server) I kept track of it. At the end, I looped through each of those weird results and hammered them until I got something that seemed stable.
I actually had no problems with chunks 1 and 2. Half way through chunk three the server started acting up so I aborted my script and added the modifications that I mentioned above. I tweaked the server and client so that they had a hard coded value for chunks 1 and 2.
From the code it was clear the password was 12 numeric digits.
Chunks of the password (of equal size, see the code) were stored in multiple chunk servers. From crashing the program or reading the code, it could be determined there were four chunk servers, so each hosted three characters of the password.
Using a port-counting vulnerability (sending multiple identical requests, and rejecting guesses where the difference in response port #s was too low: 2 for first chunk, 3 for second chunk, etc.) it was possible to brute force each chunk server in sequence.
Thus, the problem space was 4*10^3 rather than 10^12.
You only need 1 call per possible number for each chunk, yes. But since the numbers are random between 0 to 999, that averages to 500 requests per chunk. Additionally, you don't have to do any kind of port checking for the last chunk, so that saves on any overhead you might have for the port checks. Based on this I'd say there's (3x 500 requests + 20% overhead) + 500 requests for the final block. That's 2300 requests on average.
I thought you needed 2 per possible chunk? One for the base port and another to get the port difference? Though maybe that's where I was going wrong...?
You don't have to get the base port on each try, you can just re-use the previous result. I achieved this by simply storing them on the global scope and only keeping the "last" and "before last" port numbers.