Military Grade

We are presented with a simple webservice written in go which randomly changes the encryption key every 672 ms and outputs the encrypted flag. However if we analyze the seed that is used to initialize the random number generator ...

func changer() {
    ticker := time.NewTicker(time.Millisecond * 672).C
    for range ticker {
        rand.Seed(time.Now().UnixNano() & ^0x7FFFFFFFFEFFF000)

        ...
    }
}

... we find out that there are only 13 bits that are chosen randomly in the changer() function.

^0x7FFFFFFFFEFFF000
-> ^0111 1111 1111 1111 1111 1111 1111 1111 1111 1110 1111 1111 1111 0000 0000 0000
->  1000 0000 0000 0000 0000 0000 0000 0000 0000 0001 0000 0000 0000 1111 1111 1111
    ^                                               ^                ^
    63 th bit - sign                                |                |
                                                    24 th bit        |
                                                                     12 bit number

So we can simply bruteforce all possible seeds to create "random" encryption keys the same way as the main.go file until the output of the webservice decodes to something that starts with "ractf{".

package main

import (
    "bytes"
    "crypto/aes"
    "crypto/cipher"
    "encoding/hex"
    "fmt"
    "log"
    "math/rand"
    "net/http"
    "os"
    "strings"
    "sync"
    "time"
)

const rawFlag = "[REDACTED]"

var flag string
var flagmu sync.Mutex

func decrypt(ciphertextHex string, bKey []byte, blockSize int) string {
    ciphertext, err := hex.DecodeString(ciphertextHex)
    if err != nil {
        log.Println(err)
        return ""
    }

    bIV := []byte(ciphertext)[:blockSize]
    bCiphertext := []byte(ciphertext)[blockSize:]
    block, err := aes.NewCipher(bKey)
    if err != nil {
        log.Println(err)
        return ""
    }

    plaintext := make([]byte, len(bCiphertext))
    mode := cipher.NewCBCDecrypter(block, bIV)
    mode.CryptBlocks(plaintext, bCiphertext)
    return string(plaintext)
}

func main() {
    var key []byte
    var iv []byte

    for i := 0; i < 32; i++ {
        key = append(key, byte(1))
    }

    for i := 0; i < aes.BlockSize; i++ {
        iv = append(iv, byte(1))
    }

    if len(os.Args) <= 1 {
        log.Println("No ciphertext given -> Ending now")
        return
    }

    ciphertextWithoutIV := os.Args[1]
    log.Println("Decrypting "+ ciphertextWithoutIV)

    // iterate twice over the 12 bit number range
    // first with bit 24 set to 0
    // second with bit 24 set to 1
    for flipBit24 := 0; flipBit24 < 2; flipBit24++ {
        for j := 0; j < (1<<12); j++ {
            seed := int64(j);
            if flipBit24 != 0 {
                seed |= int64(1)<<24
            }

            rand.Seed(seed)
            for k := 0; k < rand.Intn(32); k++ {
                rand.Seed(rand.Int63())
            }

            var key []byte
            var iv []byte

            for k := 0; k < 32; k++ {
                key = append(key, byte(rand.Intn(255)))
            }

            for k := 0; k < aes.BlockSize; k++ {
                iv = append(iv, byte(rand.Intn(255)))
            }

            ciphertext = hex.EncodeToString(iv) + ciphertextWithoutIV

            plaintext = decrypt(ciphertext, key, aes.BlockSize)

            if strings.Index(plaintext, "ractf{") == 0 || strings.Index(plaintext, "[REDACTED]") == 0 {
                print(plaintext)
            }
        }
    }
}

social