Description: I have a pretty cool server, but it’s for QUICk people only. Nobody else is allowed.
Pro Tip: Set your ALPN to “quic-echo-example” because I forgot to remove it.
54.152.23.18:1337
Author: masond
Challenge
I didn’t solve the challenge during the ctf mainly because my lack of experience with golang and also my ability to identify the issues was affected by the lack of sleeping. Anyway this was a cool challenge made me learn about the QUIC protocol and some new things about the go language.
The title of the challenge gives us the hint that this may be a server running on the QUIC protocol also in the description we were given the ip and port to the server.
Initially I tried to use a python library for quick but I failed horribly when connecting to the server, by searching the the hint of setting the APLN to “quic-echo-example” on github I ended up searching some examples on how to connect to a QUIC server using a library named quick-go .
So what exactly is QUIC? Quic is a network-protocol designed by Jim Roskind at Google, it was mainly created to improve the performance of connection-oriented web applications using the UDP protocol instead of TCP.
Finding an example
By searching by “quic-echo-example” on github I found an example.
After this I adapted the source code to connect to the challenge server but I ended up finding a lot of difficulties during of the installation of quick-go lib, every time I tried to install it with go get . command I was receiving an odd error about a “Duplicate stream ID”. Spent a lot of time searching on the web for this and found nothing.
In the end, I ended finding out why I was having problems, I was trying to install the master branch of github and it required 1.14 version of golang… In my host machine I only had the 1.13 installed. To solve this problem I decided to use Docker.
By specifying the right version as the tag I could use the right version of golang:
1 2 3
$ ls main.go $ sudo docker run --rm -v $(pwd):/go/src/myapp -w /go/src/myapp -it golang:1.14 /bin/bash
After this I run into another problem I installed the master branch release which is unstable as fuck and also incompatible with the one running on the server. This is was when I learned about go modules, we can specify the right version with it so I searched in the github releases and the last stable release is v0.14.0:
1 2 3 4 5 6 7 8 9 10 11
$ go mod init . $ go mod edit -require github.com/lucas-clemente/quic-go@v0.14.0 $ go get -v -t . $ go build $ go install $ cat go.mod module myapp
go 1.14
require github.com/lucas-clemente/quic-go v0.14.0
And finally I was able to connect to the server:
1 2 3
$ go run main.go Client: Sending 'feqfq' Maybe you should start with Hello...
So the server replies that we should start with Hello, first we do the TLS configuration and specify the nextProtos as “quic-echo-example” as specified in the challenge description:
$ go run main.go Client: Sending 'Hello' Welcome to the super QUICk Server! You might've thought getting the flag would be easy, but it's gonna take a bit more. :D
I need some help with my Computer Architecture class, could you give me these numbers back in hex? 123454
This is the first hand of questions and is about converting decimal integers to hexa, this is where I got stuck mainly because I didn’t understand really well how golang read stream functions worked. The problem was on the number extraction, I was reading the last line with the number, but some times the number to be converted had less than 6 numbers and this is where I failed to understand the problem, when less than 6 the last line would be presented as “1234 \n” with spaces between the numbers and the new line, I was only striping the new line, because of this when sending the answer to the server everything started to hang up.
After the CTF and a day of rest I found out about the spaces and took another approach, something that I should have used since the beginning, which is using regex to extract those numbers instead of parsing them by “hand”.
functoHex(x []byte, n int)string { re := regexp.MustCompile("[0-9]+") h,err := strconv.Atoi(re.FindString(string(x))) if err != nil { panic(err) } return fmt.Sprintf("%x", h) } for i:=0; i< 1000; i++ { num := make([]byte, 7) n, err := stream.Read(num); if err != nil { return err } s := "0x"+toHex(num,n) //fmt.Printf("%d\n", i) //fmt.Printf("Received %s", num); //fmt.Printf("Sending %s\n",s) //_,err = io.WriteString(stream,s) _, err = stream.Write([]byte(s)) if err != nil { return err } } b, err := ioutil.ReadAll(stream) fmt.Printf("%s\n", b)
After converting 1000 decimal numbers we get the respective answer:
1 2 3 4 5 6 7 8 9
$ go run main.go Client: Sending 'Hello' Welcome to the super QUICk Server! You might've thought getting the flag would be easy, but it's gonna take a bit more. :D
I need some help with my Computer Architecture class, could you give me these numbers back in hex? Quickly, of course... :) Nice job, let's keep going... Can I dial you later? I'll try 6969 ;)
This time the server is trying to connect to us, so we need to turn us into a “server” and listen at the port 6969, for this we need to open a port in the router and rerun the docker container with the -p parameter to link the UDP port with the host:
1 2 3 4 5 6
$ sudo docker run --rm -p 6969:6969/udp -v (pwd):/go/src/myapp -w /go/src/myapp -it golang:1.14 /bin/bash $ go mod init . $ go mod edit -require github.com/lucas-clemente/quic-go@v0.14.0 $ go get -v -t . $ go build $ go install
Also if you have a local firewall like I have in my computer you need to open that door too, in my case I use UFW firewall:
To run the server we need to put it in another thread, we can use go-coroutines but we also have to add a code that waits for the server thread to end before quitting the main program, this can be pretty easily done with go by using sync.WaitGroup:
1 2 3 4 5 6 7 8 9 10
funcmain() { var wg sync.WaitGroup wg.Add(1) gofunc() { log.Fatal(echoServer()) }() err := clientMain() if err != nil { panic(err) } wg.Wait() }
In the code above go func() initiates the server coroutine and increases the WaitGroup counter, we put a Wait() in the end of the main function so it waits until the counter reaches the number zero. This happens when echoServer() finishes which will decrease the counter to zero.
Making the server listening at 0.0.0.0:6969 and set up TLS configurations:
// We start a server echoing data on the first stream the client opens, // then connect with a client, send the message, and wait for its receipt. funcmain() { var wg sync.WaitGroup wg.Add(1) gofunc() { log.Fatal(echoServer()) }() err := clientMain() if err != nil { panic(err) } wg.Wait() }
// Start a server that echos all data on the first stream opened by the client funcechoServer()error { listener, err := quic.ListenAddr(addrClientS, generateTLSConfig(), nil) if err != nil { return err } sess, err := listener.Accept(context.Background()) if err != nil { return err } stream, err := sess.AcceptStream(context.Background()) if err != nil { panic(err) }